Merge commit 'c689bb19cc1b3adface24e5f156081ce3cbece52' into dev-release
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index 3c31451..fcf4105 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -127,6 +127,7 @@
   id: "linux"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -140,6 +141,7 @@
   id: "linux_horizontal"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 3
   }
   buildbucket {
@@ -153,6 +155,7 @@
   id: "linux-android-4.0.4"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -180,6 +183,7 @@
   id: "linux-android-4.4.4"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -207,6 +211,7 @@
   id: "linux-android-5.1.1"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -234,6 +239,7 @@
   id: "linux-android-6.0.1"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -261,6 +267,7 @@
   id: "linux-android-7.0.0"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -288,6 +295,7 @@
   id: "linux-android-8.1.0"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -316,6 +324,7 @@
   id: "linux-android-9.0.0"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -343,6 +352,7 @@
   id: "linux-android-10.0.0"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 6
   }
   buildbucket {
@@ -390,6 +400,7 @@
   id: "linux-run-on-as-app"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 3
   }
   buildbucket {
@@ -403,6 +414,7 @@
   id: "linux-run-on-as-app-recompilation"
   acl_sets: "default"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 3
   }
   buildbucket {
@@ -430,6 +442,9 @@
 job {
   id: "linux-jctf"
   acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -467,6 +482,9 @@
 job {
   id: "r8cf-linux-jctf"
   acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -490,6 +508,7 @@
 job {
   id: "windows"
   triggering_policy: {
+    kind: GREEDY_BATCHING
     max_concurrent_invocations: 3
   }
   acl_sets: "default"
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 6a2a8c5..f3acbec 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfThrow;
@@ -148,7 +149,7 @@
               ParameterAnnotationsList.empty(),
               code,
               false,
-              50,
+              CfVersion.V1_6,
               false);
       if (method.isStatic() || method.isDirectMethod()) {
         directMethods.add(throwingMethod);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ab78b10..5e8e3a6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -85,7 +85,6 @@
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ClassInitFieldSynthesizer;
-import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
 import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
 import com.android.tools.r8.shaking.DiscardedChecker;
 import com.android.tools.r8.shaking.Enqueuer;
@@ -99,6 +98,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationUtils;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import com.android.tools.r8.shaking.StaticClassMerger;
 import com.android.tools.r8.shaking.TreePruner;
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
@@ -324,8 +324,8 @@
       timing.begin("Strip unused code");
       Set<DexType> classesToRetainInnerClassAttributeFor = null;
       Set<DexType> missingClasses = null;
-      ClassMergingEnqueuerExtension classMergingEnqueuerExtension =
-          new ClassMergingEnqueuerExtension(appView.dexItemFactory());
+      RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
+          new RuntimeTypeCheckInfo.Builder(appView.dexItemFactory());
       try {
         // TODO(b/154849103): Find a better way to determine missing classes.
         missingClasses = new SubtypingInfo(appView).getMissingClasses();
@@ -379,7 +379,8 @@
                 executorService,
                 appView,
                 subtypingInfo,
-                classMergingEnqueuerExtension);
+                classMergingEnqueuerExtensionBuilder);
+
         assert appView.rootSet().verifyKeptFieldsAreAccessedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptMethodsAreTargetedAndLive(appViewWithLiveness.appInfo());
         assert appView.rootSet().verifyKeptTypesAreLive(appViewWithLiveness.appInfo());
@@ -428,6 +429,8 @@
           annotationRemover.ensureValid().run();
           classesToRetainInnerClassAttributeFor =
               annotationRemover.getClassesToRetainInnerClassAttributeFor();
+          new GenericSignatureRewriter(appView, NamingLens.getIdentityLens())
+              .run(appView.appInfo().classes(), executorService);
         }
       } finally {
         timing.end();
@@ -481,7 +484,7 @@
       }
 
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      appView.setGraphLens(new MemberRebindingAnalysis(appViewWithLiveness).run());
+      appView.setGraphLens(new MemberRebindingAnalysis(appViewWithLiveness).run(executorService));
       appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView);
 
       if (options.shouldDesugarNests()) {
@@ -506,6 +509,7 @@
       boolean isKotlinLibraryCompilationWithInlinePassThrough =
           options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
 
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo = classMergingEnqueuerExtensionBuilder.build();
       if (!isKotlinLibraryCompilationWithInlinePassThrough
           && options.getProguardConfiguration().isOptimizing()) {
         if (options.enableStaticClassMerging) {
@@ -529,6 +533,7 @@
           if (lens != null) {
             appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
             appView.rewriteWithLens(lens);
+            runtimeTypeCheckInfo = runtimeTypeCheckInfo.rewriteWithLens(lens);
           }
           timing.end();
         }
@@ -564,7 +569,7 @@
           timing.begin("HorizontalClassMerger");
           HorizontalClassMerger merger =
               new HorizontalClassMerger(
-                  appViewWithLiveness, mainDexTracingResult, classMergingEnqueuerExtension);
+                  appViewWithLiveness, mainDexTracingResult, runtimeTypeCheckInfo);
           DirectMappedDexApplication.Builder appBuilder =
               appView.appInfo().app().asDirect().builder();
           HorizontalClassMergerGraphLens lens = merger.run(appBuilder);
@@ -572,12 +577,13 @@
             DirectMappedDexApplication app = appBuilder.build();
             appView.removePrunedClasses(app, appView.horizontallyMergedClasses().getSources());
             appView.rewriteWithLens(lens);
+
+            // Only required for class merging, clear instance to save memory.
+            runtimeTypeCheckInfo = null;
           }
           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.
@@ -998,7 +1004,7 @@
       ExecutorService executorService,
       AppView<AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
-      ClassMergingEnqueuerExtension classMergingEnqueuerExtension)
+      RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder)
       throws ExecutionException {
     Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
     enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
@@ -1012,7 +1018,7 @@
     }
 
     if (options.isClassMergingExtensionRequired()) {
-      classMergingEnqueuerExtension.attach(enqueuer);
+      classMergingEnqueuerExtensionBuilder.attach(enqueuer);
     }
 
     AppView<AppInfoWithLiveness> appViewWithLiveness =
diff --git a/src/main/java/com/android/tools/r8/cf/CfVersion.java b/src/main/java/com/android/tools/r8/cf/CfVersion.java
new file mode 100644
index 0000000..84228b9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/CfVersion.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.cf;
+
+import java.util.Comparator;
+import org.objectweb.asm.Opcodes;
+
+public final class CfVersion implements Comparable<CfVersion> {
+
+  public static final CfVersion V1_1 = new CfVersion(Opcodes.V1_1);
+  public static final CfVersion V1_2 = new CfVersion(Opcodes.V1_2);
+  public static final CfVersion V1_3 = new CfVersion(Opcodes.V1_3);
+  public static final CfVersion V1_4 = new CfVersion(Opcodes.V1_4);
+  public static final CfVersion V1_5 = new CfVersion(Opcodes.V1_5);
+  public static final CfVersion V1_6 = new CfVersion(Opcodes.V1_6);
+  public static final CfVersion V1_7 = new CfVersion(Opcodes.V1_7);
+  public static final CfVersion V1_8 = new CfVersion(Opcodes.V1_8);
+  public static final CfVersion V9 = new CfVersion(Opcodes.V9);
+  public static final CfVersion V10 = new CfVersion(Opcodes.V10);
+  public static final CfVersion V11 = new CfVersion(Opcodes.V11);
+
+  private final int version;
+
+  // Private constructor in case we want to canonicalize versions.
+  private CfVersion(int version) {
+    this.version = version;
+  }
+
+  public static CfVersion fromRaw(int rawVersion) {
+    return new CfVersion(rawVersion);
+  }
+
+  public int major() {
+    return version & 0xFFFF;
+  }
+
+  public int minor() {
+    return version >> 16;
+  }
+
+  public int raw() {
+    return version;
+  }
+
+  public static CfVersion maxAllowNull(CfVersion v1, CfVersion v2) {
+    assert v1 != null || v2 != null;
+    if (v1 == null) {
+      return v2;
+    }
+    if (v2 == null) {
+      return v1;
+    }
+    return v1.max(v2);
+  }
+
+  public CfVersion max(CfVersion other) {
+    return isLessThan(other) ? other : this;
+  }
+
+  public boolean isEqual(CfVersion other) {
+    return version == other.version;
+  }
+
+  public boolean isLessThan(CfVersion other) {
+    return compareTo(other) < 0;
+  }
+
+  public boolean isLessThanOrEqual(CfVersion other) {
+    return compareTo(other) <= 0;
+  }
+
+  public boolean isGreaterThan(CfVersion other) {
+    return compareTo(other) > 0;
+  }
+
+  public boolean isGreaterThanOrEqual(CfVersion other) {
+    return compareTo(other) >= 0;
+  }
+
+  @Override
+  public int compareTo(CfVersion o) {
+    return Comparator.comparingInt(CfVersion::major)
+        .thenComparingInt(CfVersion::minor)
+        .compare(this, o);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof CfVersion)) {
+      return false;
+    }
+    return isEqual((CfVersion) o);
+  }
+
+  @Override
+  public int hashCode() {
+    return version;
+  }
+
+  @Override
+  public String toString() {
+    return minor() != 0 ? ("" + major() + "." + minor()) : ("" + major());
+  }
+}
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 1328f67..c05c20d 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -193,7 +192,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forBinop();
   }
 
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 232f7c1..70b41e1 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -68,7 +67,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forArrayLength();
   }
 
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 411ea64..ae086f3 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -114,7 +113,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forArrayGet();
   }
 
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 585b4bc..eb43f6b 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -104,7 +103,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forArrayPut();
   }
 
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 4dfa1c5..4661e68 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
@@ -8,7 +8,6 @@
 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.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -86,7 +85,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forCheckCast(type, context);
   }
 
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 cc83558..23b0c8b 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -118,7 +117,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forBinop();
   }
 
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 43a3a4f..51f34fc 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
@@ -9,7 +9,6 @@
 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.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -109,7 +108,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forConstClass(type, context);
   }
 
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 9a22ae7..259a711 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
@@ -9,7 +9,6 @@
 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;
@@ -87,7 +86,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forConstMethodHandle();
   }
 
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 632a24c..70417f6 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
@@ -8,7 +8,6 @@
 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;
@@ -85,7 +84,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forConstMethodType();
   }
 
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 f6a720d..1835f18 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -60,7 +59,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forConstInstruction();
   }
 
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 6f7fcf4..e48643e 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -161,7 +160,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forConstInstruction();
   }
 
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 1c9305b..ee042dc 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -90,7 +89,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forConstInstruction();
   }
 
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 eed2ef1..e9f11a8 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
@@ -9,7 +9,6 @@
 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;
@@ -107,7 +106,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forDexItemBasedConstString(item, context);
   }
 
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 30bb5cc..7c28eff 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 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;
@@ -154,7 +153,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     switch (opcode) {
       case Opcodes.GETSTATIC:
         return inliningConstraints.forStaticGet(field, context);
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 dfa633c..cdcb3ec 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
@@ -11,7 +11,6 @@
 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;
@@ -440,7 +439,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return ConstraintWithTarget.ALWAYS;
   }
 
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 f3babee..0867a89 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -85,7 +84,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forJumpInstruction();
   }
 
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 433d5d9..ddca593 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -121,7 +120,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forJumpInstruction();
   }
 
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 bf76936..efedff8 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -122,7 +121,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forJumpInstruction();
   }
 
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 2d0e0f0..9e59ba6 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -80,7 +79,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return ConstraintWithTarget.ALWAYS;
   }
 
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 cb0d50f..e9c06e0 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 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;
@@ -95,7 +94,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forInitClass(clazz, context);
   }
 
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 46d56b4..1300458 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
@@ -8,7 +8,6 @@
 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.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -94,7 +93,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forInstanceOf(type, context);
   }
 
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 0616bed..b8874e8 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
@@ -9,7 +9,6 @@
 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;
@@ -226,7 +225,7 @@
   }
 
   public abstract ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context);
+      InliningConstraints inliningConstraints, ProgramMethod context);
 
   public abstract void evaluate(
       CfFrameVerificationHelper frameBuilder,
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 3ad8ed2..4765be5 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
@@ -249,7 +249,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     GraphLens graphLens = inliningConstraints.getGraphLens();
     AppView<?> appView = inliningConstraints.getAppView();
     DexMethod target = method;
@@ -259,17 +259,17 @@
       case Opcodes.INVOKEINTERFACE:
         // Could have changed to an invoke-virtual instruction due to vertical class merging
         // (if an interface is merged into a class).
-        type = graphLens.lookupMethod(target, null, Type.INTERFACE).getType();
+        type = graphLens.lookupMethod(target, context.getReference(), Type.INTERFACE).getType();
         assert type == Type.INTERFACE || type == Type.VIRTUAL;
         break;
 
       case Opcodes.INVOKESPECIAL:
         if (appView.dexItemFactory().isConstructor(target)) {
           type = Type.DIRECT;
-          assert noNeedToUseGraphLens(target, type, graphLens);
-        } else if (target.holder == context.type) {
+          assert noNeedToUseGraphLens(target, context.getReference(), type, graphLens);
+        } else if (target.holder == context.getHolderType()) {
           // The method could have been publicized.
-          type = graphLens.lookupMethod(target, null, Type.DIRECT).getType();
+          type = graphLens.lookupMethod(target, context.getReference(), Type.DIRECT).getType();
           assert type == Type.DIRECT || type == Type.VIRTUAL;
         } else {
           // This is a super call. Note that the vertical class merger translates some invoke-super
@@ -280,14 +280,15 @@
           // TODO(christofferqa): Consider using graphLens.lookupMethod (to do this, we need the
           // context for the graph lens, though).
           type = Type.SUPER;
-          assert noNeedToUseGraphLens(target, type, graphLens);
+          assert noNeedToUseGraphLens(target, context.getReference(), type, graphLens);
         }
         break;
 
       case Opcodes.INVOKESTATIC:
         {
           // Static invokes may have changed as a result of horizontal class merging.
-          MethodLookupResult lookup = graphLens.lookupMethod(target, null, Type.STATIC);
+          MethodLookupResult lookup =
+              graphLens.lookupMethod(target, context.getReference(), Type.STATIC);
           target = lookup.getReference();
           type = lookup.getType();
         }
@@ -298,7 +299,7 @@
           type = Type.VIRTUAL;
           // Instructions that target a private method in the same class translates to
           // invoke-direct.
-          if (target.holder == context.type) {
+          if (target.holder == context.getHolderType()) {
             DexClass clazz = appView.definitionFor(target.holder);
             if (clazz != null && clazz.lookupDirectMethod(target) != null) {
               type = Type.DIRECT;
@@ -306,7 +307,7 @@
           }
 
           // Virtual invokes may have changed to interface invokes as a result of member rebinding.
-          MethodLookupResult lookup = graphLens.lookupMethod(target, null, type);
+          MethodLookupResult lookup = graphLens.lookupMethod(target, context.getReference(), type);
           target = lookup.getReference();
           type = lookup.getType();
         }
@@ -343,8 +344,8 @@
   }
 
   private static boolean noNeedToUseGraphLens(
-      DexMethod method, Invoke.Type type, GraphLens graphLens) {
-    assert graphLens.lookupMethod(method, null, type).getType() == type;
+      DexMethod method, DexMethod context, Invoke.Type type, GraphLens graphLens) {
+    assert graphLens.lookupMethod(method, context, type).getType() == type;
     return true;
   }
 
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 2651e10..add492c 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
@@ -11,7 +11,6 @@
 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.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
@@ -141,7 +140,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forInvokeCustom();
   }
 
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 eee91ae..ed6c16f 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
@@ -9,7 +9,6 @@
 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;
@@ -71,7 +70,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     throw error();
   }
 
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 5701493..2bfdd93 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -83,7 +82,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return ConstraintWithTarget.ALWAYS;
   }
 
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 679e75d..3160d6e 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -112,7 +111,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forLoad();
   }
 
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 b3b8a3c..d7f2c7d 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -165,7 +164,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forBinop();
   }
 
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 c947e17..8225673 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -82,7 +81,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forMonitor();
   }
 
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 2209a80..5f405d3 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
@@ -8,7 +8,6 @@
 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.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -95,7 +94,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forInvokeMultiNewArray(type, context);
   }
 
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 6da10c4..a7b8c32 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -107,7 +106,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forUnop();
   }
 
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 3913b83..60da1b2 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
@@ -9,7 +9,6 @@
 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.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -83,7 +82,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forNewInstance(type, context);
   }
 
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 593abb4..30671bd 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
@@ -9,7 +9,6 @@
 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.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -133,7 +132,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forNewArrayEmpty(type, context);
   }
 
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 f30e52e..87123a4 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -64,7 +63,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return ConstraintWithTarget.ALWAYS;
   }
 
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 abe5ad1..a7e60e4 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -178,7 +177,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forUnop();
   }
 
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 2d8c11d..233a3ef 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -94,7 +93,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return ConstraintWithTarget.ALWAYS;
   }
 
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 9d8df3b..8305860 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -100,7 +99,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forReturn();
   }
 
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 de43a8d..687d903 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -74,7 +73,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forReturn();
   }
 
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 109df7f..c28cb32 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -341,7 +340,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return ConstraintWithTarget.ALWAYS;
   }
 
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 51bc1b7..debb573 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -112,7 +111,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forStore();
   }
 
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 9e883cf..ddfd927 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -137,7 +136,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forJumpInstruction();
   }
 
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 b00ef94..b165711 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -81,7 +80,7 @@
 
   @Override
   public ConstraintWithTarget inliningConstraint(
-      InliningConstraints inliningConstraints, DexProgramClass context) {
+      InliningConstraints inliningConstraints, ProgramMethod context) {
     return inliningConstraints.forJumpInstruction();
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index 77a82df..e3801e6 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.cf.CfVersion;
+
 public class Constants {
 
   public static final byte[] DEX_FILE_MAGIC_PREFIX = {'d', 'e', 'x', '\n'};
@@ -16,7 +18,7 @@
   public static final int MAX_VDEX_VERSION = 11;
 
   // We apply Java 6 class file constraints on DEX files.
-  public static final int CORRESPONDING_CLASS_FILE_VERSION = 50;
+  public static final CfVersion CORRESPONDING_CLASS_FILE_VERSION = CfVersion.V1_6;
 
   public static final int DEX_MAGIC_SIZE = 8;
 
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index b4852a3..c915fe7 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -27,6 +27,7 @@
   public static final String SHA1 = "sha-1";
   public static final String COMPILATION_MODE = "compilation-mode";
   public static final String HAS_CHECKSUMS = "has-checksums";
+  public static final String BACKEND = "backend";
   public static final String PG_MAP_ID = "pg-map-id";
   public static final String R8_MODE = "r8-mode";
   private static final String NO_LIBRARY_DESUGARING = "<no-library-desugaring>";
@@ -42,6 +43,11 @@
     }
   }
 
+  public enum Backend {
+    CF,
+    DEX
+  }
+
   private static final char PREFIX_CHAR = '~';
   private static final String PREFIX = "~~";
   private static final String D8_PREFIX = PREFIX + Tool.D8 + "{";
@@ -198,6 +204,24 @@
     return this;
   }
 
+  public boolean hasBackend() {
+    return jsonObject.has(BACKEND);
+  }
+
+  public String getBackend() {
+    if (!hasBackend()) {
+      // Before adding backend we would always compile to dex if min-api was specified.
+      return hasMinApi() ? "dex" : "cf";
+    }
+    return jsonObject.get(BACKEND).getAsString();
+  }
+
+  public Marker setBackend(Backend backend) {
+    assert !hasBackend();
+    jsonObject.addProperty(BACKEND, backend.name().toLowerCase());
+    return this;
+  }
+
   public boolean getHasChecksums() {
     return jsonObject.get(HAS_CHECKSUMS).getAsBoolean();
   }
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 a7bd6c1..877f2ba 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
+import com.android.tools.r8.optimize.MemberRebindingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -598,17 +599,26 @@
     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();
+    // temporarily replace that lens with a lens that does not have any rewritings to avoid the
+    // overhead of traversing the entire lens chain upon each lookup during the rewriting.
+    NonIdentityGraphLens firstUnappliedLens = lens;
+    while (firstUnappliedLens.getPrevious() != appliedLens) {
+      GraphLens previousLens = firstUnappliedLens.getPrevious();
       assert previousLens.isNonIdentityLens();
-      temporaryRootLens = previousLens.asNonIdentityLens();
+      firstUnappliedLens = previousLens.asNonIdentityLens();
     }
 
-    temporaryRootLens.withAlternativeParentLens(
-        GraphLens.getIdentityLens(),
+    // Insert a member rebinding lens above the first unapplied lens.
+    MemberRebindingLens appliedMemberRebindingLens =
+        firstUnappliedLens.findPrevious(GraphLens::isMemberRebindingLens);
+    GraphLens newMemberRebindingLens =
+        appliedMemberRebindingLens != null
+            ? appliedMemberRebindingLens.toRewrittenFieldRebindingLens(
+                appView.dexItemFactory(), appliedLens)
+            : GraphLens.getIdentityLens();
+
+    firstUnappliedLens.withAlternativeParentLens(
+        newMemberRebindingLens,
         () -> {
           appView.setAppInfo(appView.appInfo().rewrittenWithLens(application, lens));
           appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
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 a08d65e..e08f5da 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -7,10 +7,9 @@
 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.CfVersion;
 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;
@@ -285,7 +284,7 @@
 
   public void write(
       ProgramMethod method,
-      int classFileVersion,
+      CfVersion classFileVersion,
       AppView<?> appView,
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
@@ -305,8 +304,9 @@
     }
     for (CfInstruction instruction : instructions) {
       if (instruction instanceof CfFrame
-          && (classFileVersion <= V1_5
-              || (classFileVersion == V1_6 && !options.shouldKeepStackMapTable()))) {
+          && (classFileVersion.isLessThan(CfVersion.V1_6)
+              || (classFileVersion.isEqual(CfVersion.V1_6)
+                  && !options.shouldKeepStackMapTable()))) {
         continue;
       }
       instruction.write(
@@ -618,7 +618,7 @@
       ProgramMethod method,
       AppView<AppInfoWithLiveness> appView,
       GraphLens graphLens,
-      DexProgramClass context) {
+      ProgramMethod context) {
     InliningConstraints inliningConstraints = new InliningConstraints(appView, graphLens);
     if (appView.options().isInterfaceMethodDesugaringEnabled()) {
       // TODO(b/120130831): Conservatively need to say "no" at this point if there are invocations
@@ -687,7 +687,7 @@
       stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
       return true;
     }
-    if (method.hasClassFileVersion() && method.getClassFileVersion() <= V1_6) {
+    if (method.hasClassFileVersion() && method.getClassFileVersion().isLessThan(CfVersion.V1_7)) {
       stackMapStatus = StackMapStatus.INVALID_OR_NOT_PRESENT;
       return true;
     }
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 36f43ba..e3584ec 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -105,16 +106,17 @@
    * Checks whether the constraints from
    * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
    */
-  public boolean areValid(int majorVersion, boolean isPackageInfo) {
+  public boolean areValid(CfVersion version, boolean isPackageInfo) {
     if (isInterface()) {
       // We ignore the super flags prior to JDK 9, as so did the VM.
-      if ((majorVersion >= 53) && isSuper()) {
+      if (version.isGreaterThanOrEqual(CfVersion.V9) && isSuper()) {
         return false;
       }
       // When not coming from DEX input we require interfaces to be abstract - except for
       // package-info classes - as both old versions of javac and other tools can produce
       // package-info classes that are interfaces but not abstract.
-      if (((majorVersion > Constants.CORRESPONDING_CLASS_FILE_VERSION) && !isAbstract())
+      if (version.isGreaterThanOrEqual(Constants.CORRESPONDING_CLASS_FILE_VERSION)
+          && !isAbstract()
           && !isPackageInfo) {
         return false;
       }
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 a972aed..ea4103f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -12,6 +12,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -67,6 +68,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -149,7 +151,7 @@
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
   private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
-  private int classFileVersion;
+  private CfVersion classFileVersion = null;
   private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
   /** Generic signature information if the attribute is present in the input */
   private MethodTypeSignature genericSignature;
@@ -241,7 +243,7 @@
         parameterAnnotationsList,
         code,
         false,
-        -1);
+        null);
   }
 
   public DexEncodedMethod(
@@ -260,7 +262,7 @@
         parameterAnnotationsList,
         code,
         d8R8Synthesized,
-        -1);
+        null);
   }
 
   public DexEncodedMethod(
@@ -271,7 +273,7 @@
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
       boolean d8R8Synthesized,
-      int classFileVersion) {
+      CfVersion classFileVersion) {
     this(
         method,
         accessFlags,
@@ -292,7 +294,7 @@
       ParameterAnnotationsList parameterAnnotationsList,
       Code code,
       boolean d8R8Synthesized,
-      int classFileVersion,
+      CfVersion classFileVersion,
       boolean deprecated) {
     super(annotations);
     this.method = method;
@@ -783,21 +785,21 @@
     code = null;
   }
 
-  public int getClassFileVersion() {
+  public CfVersion getClassFileVersion() {
     checkIfObsolete();
-    assert classFileVersion >= 0;
+    assert classFileVersion != null;
     return classFileVersion;
   }
 
   public boolean hasClassFileVersion() {
     checkIfObsolete();
-    return classFileVersion >= 0;
+    return classFileVersion != null;
   }
 
-  public void upgradeClassFileVersion(int version) {
+  public void upgradeClassFileVersion(CfVersion version) {
     checkIfObsolete();
-    assert version >= 0;
-    classFileVersion = Math.max(classFileVersion, version);
+    assert version != null;
+    classFileVersion = CfVersion.maxAllowNull(classFileVersion, version);
   }
 
   public String qualifiedName() {
@@ -1366,6 +1368,10 @@
     return method;
   }
 
+  public MethodPosition getPosition() {
+    return new MethodPosition(method.asMethodReference());
+  }
+
   @Override
   public boolean isDexEncodedMethod() {
     checkIfObsolete();
@@ -1423,7 +1429,7 @@
 
   public void copyMetadata(DexEncodedMethod from) {
     checkIfObsolete();
-    if (from.classFileVersion > classFileVersion) {
+    if (from.hasClassFileVersion()) {
       upgradeClassFileVersion(from.getClassFileVersion());
     }
   }
@@ -1461,7 +1467,7 @@
     private CompilationState compilationState;
     private MethodOptimizationInfo optimizationInfo;
     private KotlinMethodLevelInfo kotlinMemberInfo;
-    private final int classFileVersion;
+    private final CfVersion classFileVersion;
     private boolean d8R8Synthesized;
 
     private Builder(DexEncodedMethod from) {
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 1a8d506..6474140 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -47,7 +49,7 @@
 
   private final ProgramResource.Kind originKind;
   private final Collection<DexProgramClass> synthesizedFrom;
-  private int initialClassFileVersion = -1;
+  private CfVersion initialClassFileVersion = null;
   private boolean deprecated = false;
   private KotlinClassLevelInfo kotlinInfo = NO_KOTLIN_INFO;
 
@@ -157,6 +159,18 @@
         predicate, method -> consumer.accept(new ProgramMethod(this, method)));
   }
 
+  public Iterable<ProgramMethod> directProgramMethods() {
+    return Iterables.transform(directMethods(), method -> new ProgramMethod(this, method));
+  }
+
+  public Iterable<ProgramMethod> directProgramMethods(Predicate<DexEncodedMethod> predicate) {
+    return Iterables.transform(directMethods(predicate), method -> new ProgramMethod(this, method));
+  }
+
+  public Iterable<ProgramMethod> programInstanceInitializers() {
+    return directProgramMethods(DexEncodedMethod::isInstanceInitializer);
+  }
+
   public void forEachProgramDirectMethod(Consumer<ProgramMethod> consumer) {
     forEachProgramDirectMethodMatching(alwaysTrue(), consumer);
   }
@@ -552,17 +566,18 @@
     return this;
   }
 
-  public void setInitialClassFileVersion(int initialClassFileVersion) {
-    assert this.initialClassFileVersion == -1 && initialClassFileVersion > 0;
+  public void setInitialClassFileVersion(CfVersion initialClassFileVersion) {
+    assert this.initialClassFileVersion == null;
+    assert initialClassFileVersion != null;
     this.initialClassFileVersion = initialClassFileVersion;
   }
 
   public boolean hasClassFileVersion() {
-    return initialClassFileVersion > -1;
+    return initialClassFileVersion != null;
   }
 
-  public int getInitialClassFileVersion() {
-    assert initialClassFileVersion > -1;
+  public CfVersion getInitialClassFileVersion() {
+    assert initialClassFileVersion != null;
     return initialClassFileVersion;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index ecb09ca..977d06d 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -21,6 +21,10 @@
     return null;
   }
 
+  public DexField getResolvedFieldReference() {
+    return null;
+  }
+
   public boolean isSuccessfulResolution() {
     return false;
   }
@@ -78,6 +82,11 @@
     }
 
     @Override
+    public DexField getResolvedFieldReference() {
+      return resolvedField.toReference();
+    }
+
+    @Override
     public DexEncodedField getResolvedMember() {
       return resolvedField;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
new file mode 100644
index 0000000..b01445d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
@@ -0,0 +1,151 @@
+// 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.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import java.util.List;
+
+public class GenericSignatureEnqueuerAnalysis extends EnqueuerAnalysis {
+
+  private final GenericSignatureVisitor visitor;
+
+  public GenericSignatureEnqueuerAnalysis(DexDefinitionSupplier definitionSupplier) {
+    visitor = new GenericSignatureTypeVisitor(definitionSupplier);
+  }
+
+  @Override
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
+    visitor.visitClassSignature(clazz.getClassSignature());
+  }
+
+  @Override
+  public void processNewlyLiveField(ProgramField field) {
+    visitor.visitFieldTypeSignature(field.getDefinition().getGenericSignature());
+  }
+
+  @Override
+  public void processNewlyLiveMethod(ProgramMethod method) {
+    visitor.visitMethodSignature(method.getDefinition().getGenericSignature());
+  }
+
+  private static class GenericSignatureTypeVisitor implements GenericSignatureVisitor {
+
+    private final DexDefinitionSupplier definitionSupplier;
+
+    private GenericSignatureTypeVisitor(DexDefinitionSupplier definitionSupplier) {
+      this.definitionSupplier = definitionSupplier;
+    }
+
+    @Override
+    public void visitClassSignature(ClassSignature classSignature) {
+      if (classSignature.hasNoSignature()) {
+        return;
+      }
+      classSignature.visit(this);
+    }
+
+    @Override
+    public void visitMethodSignature(MethodTypeSignature methodSignature) {
+      if (methodSignature.hasNoSignature()) {
+        return;
+      }
+      methodSignature.visit(this);
+    }
+
+    @Override
+    public void visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+      if (fieldSignature.hasNoSignature()) {
+        return;
+      }
+      if (fieldSignature.isStar()) {
+        return;
+      }
+      if (fieldSignature.isTypeVariableSignature()) {
+        return;
+      }
+      if (fieldSignature.isArrayTypeSignature()) {
+        fieldSignature.asArrayTypeSignature().visit(this);
+        return;
+      }
+      assert fieldSignature.isClassTypeSignature();
+      visitClassTypeSignature(fieldSignature.asClassTypeSignature());
+    }
+
+    private void visitClassTypeSignature(ClassTypeSignature classTypeSignature) {
+      definitionSupplier.definitionFor(classTypeSignature.type);
+      classTypeSignature.visit(this);
+    }
+
+    @Override
+    public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) {
+      formalTypeParameters.forEach(formalTypeParameter -> formalTypeParameter.visit(this));
+    }
+
+    @Override
+    public void visitClassBound(FieldTypeSignature fieldSignature) {
+      visitFieldTypeSignature(fieldSignature);
+    }
+
+    @Override
+    public void visitInterfaceBound(FieldTypeSignature fieldSignature) {
+      visitFieldTypeSignature(fieldSignature);
+    }
+
+    @Override
+    public void visitSuperClass(ClassTypeSignature classTypeSignature) {
+      visitClassTypeSignature(classTypeSignature);
+    }
+
+    @Override
+    public void visitSuperInterface(ClassTypeSignature classTypeSignature) {
+      visitClassTypeSignature(classTypeSignature);
+    }
+
+    @Override
+    public void visitTypeSignature(TypeSignature typeSignature) {
+      if (typeSignature.isBaseTypeSignature()) {
+        return;
+      }
+      assert typeSignature.isFieldTypeSignature();
+      visitFieldTypeSignature(typeSignature.asFieldTypeSignature());
+    }
+
+    @Override
+    public void visitSimpleClass(ClassTypeSignature classTypeSignature) {
+      visitClassTypeSignature(classTypeSignature);
+    }
+
+    @Override
+    public void visitReturnType(ReturnType returnType) {
+      if (returnType.isVoidDescriptor()) {
+        return;
+      }
+      visitTypeSignature(returnType.typeSignature);
+    }
+
+    @Override
+    public void visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
+      typeSignatures.forEach(this::visitTypeSignature);
+    }
+
+    @Override
+    public void visitThrowsSignatures(List<TypeSignature> typeSignatures) {
+      typeSignatures.forEach(this::visitTypeSignature);
+    }
+
+    @Override
+    public void visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+      typeArguments.forEach(this::visitFieldTypeSignature);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 7804c5f..e9b45f1 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -38,21 +38,21 @@
   }
 
   public ClassSignature rewrite(ClassSignature classSignature) {
-    if (classSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+    if (classSignature.hasNoSignature()) {
       return classSignature;
     }
     return new ClassSignatureRewriter().run(classSignature);
   }
 
   public FieldTypeSignature rewrite(FieldTypeSignature fieldTypeSignature) {
-    if (fieldTypeSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+    if (fieldTypeSignature.hasNoSignature()) {
       return fieldTypeSignature;
     }
     return new TypeSignatureRewriter().run(fieldTypeSignature);
   }
 
   public MethodTypeSignature rewrite(MethodTypeSignature methodTypeSignature) {
-    if (methodTypeSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+    if (methodTypeSignature.hasNoSignature()) {
       return methodTypeSignature;
     }
     return new MethodTypeSignatureRewriter().run(methodTypeSignature);
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 f2de6db..ac9b854 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -28,6 +28,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
 
 /**
  * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until
@@ -320,6 +321,13 @@
     return newMethod.lookupOnProgramClass(holder);
   }
 
+  // Predicate indicating if a rewritten type is a simple renaming, meaning the move from type to
+  // rewritten is just a renaming of the type to another. In other words, the content of the
+  // definition, including the definition of all of its members is the same modulo the renaming.
+  public boolean isSimpleRenaming(DexType from, DexType to) {
+    return false;
+  }
+
   public abstract DexType lookupClassType(DexType type);
 
   public abstract DexType lookupType(DexType type);
@@ -348,15 +356,21 @@
   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);
+    return lookupFieldResult(field).getReference();
   }
 
-  protected abstract DexField internalLookupField(
+  /** Lookup a rebound or non-rebound field reference using the current graph lens. */
+  public FieldLookupResult lookupFieldResult(DexField field) {
+    // Lookup the field using the graph lens and return the lookup result.
+    return internalLookupField(field, x -> x);
+  }
+
+  protected abstract FieldLookupResult internalLookupField(
       DexField reference, LookupFieldContinuation continuation);
 
   interface LookupFieldContinuation {
 
-    DexField lookupField(FieldLookupResult previous);
+    FieldLookupResult lookupField(FieldLookupResult previous);
   }
 
   public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
@@ -408,6 +422,10 @@
 
   public abstract boolean isIdentityLens();
 
+  public boolean isMemberRebindingLens() {
+    return false;
+  }
+
   public abstract boolean isNonIdentityLens();
 
   public NonIdentityGraphLens asNonIdentityLens() {
@@ -616,6 +634,19 @@
       return previousLens;
     }
 
+    @SuppressWarnings("unchecked")
+    public final <T extends GraphLens> T findPrevious(Predicate<NonIdentityGraphLens> predicate) {
+      GraphLens current = getPrevious();
+      while (current.isNonIdentityLens()) {
+        NonIdentityGraphLens nonIdentityGraphLens = current.asNonIdentityLens();
+        if (predicate.test(nonIdentityGraphLens)) {
+          return (T) nonIdentityGraphLens;
+        }
+        current = nonIdentityGraphLens.getPrevious();
+      }
+      return null;
+    }
+
     public final void withAlternativeParentLens(GraphLens lens, Action action) {
       GraphLens oldParent = getPrevious();
       previousLens = lens;
@@ -663,7 +694,7 @@
     }
 
     @Override
-    protected DexField internalLookupField(
+    protected FieldLookupResult internalLookupField(
         DexField reference, LookupFieldContinuation continuation) {
       return previousLens.internalLookupField(
           reference, previous -> continuation.lookupField(internalDescribeLookupField(previous)));
@@ -772,7 +803,7 @@
     }
 
     @Override
-    protected DexField internalLookupField(
+    protected FieldLookupResult 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.
@@ -847,7 +878,7 @@
     }
 
     @Override
-    protected DexField internalLookupField(
+    protected FieldLookupResult internalLookupField(
         DexField reference, LookupFieldContinuation continuation) {
       return getIdentityLens().internalLookupField(reference, continuation);
     }
@@ -981,12 +1012,15 @@
       if (previous.hasReboundReference()) {
         // Rewrite the rebound reference and then "fixup" the non-rebound reference.
         DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
+        DexField rewrittenNonReboundReference =
+            previous.getReference() == previous.getReboundReference()
+                ? rewrittenReboundReference
+                : rewrittenReboundReference.withHolder(
+                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                    dexItemFactory);
         return FieldLookupResult.builder(this)
             .setReboundReference(rewrittenReboundReference)
-            .setReference(
-                rewrittenReboundReference.withHolder(
-                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
-                    dexItemFactory))
+            .setReference(rewrittenNonReboundReference)
             .build();
       } else {
         // TODO(b/168282032): We should always have the rebound reference, so this should become
@@ -1011,8 +1045,8 @@
                     internalDescribeLookupClassType(previous.getReference().getHolderType()),
                     dexItemFactory);
         return MethodLookupResult.builder(this)
-            .setReboundReference(rewrittenReboundReference)
             .setReference(rewrittenReference)
+            .setReboundReference(rewrittenReboundReference)
             .setPrototypeChanges(
                 internalDescribePrototypeChanges(
                     previous.getPrototypeChanges(), rewrittenReboundReference))
@@ -1090,18 +1124,18 @@
      *
      * <p>Handle methods moved from interface to class or class to interface.
      */
-    protected final Type mapVirtualInterfaceInvocationTypes(
+    public static Type mapVirtualInterfaceInvocationTypes(
         DexDefinitionSupplier definitions,
         DexMethod newMethod,
         DexMethod originalMethod,
         Type type) {
       if (type == Type.VIRTUAL || type == Type.INTERFACE) {
         // Get the invoke type of the actual definition.
-        DexClass newTargetClass = definitions.definitionFor(newMethod.holder);
+        DexClass newTargetClass = definitions.definitionFor(newMethod.getHolderType());
         if (newTargetClass == null) {
           return type;
         }
-        DexClass originalTargetClass = definitions.definitionFor(originalMethod.holder);
+        DexClass originalTargetClass = definitions.definitionFor(originalMethod.getHolderType());
         if (originalTargetClass != null
             && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
           // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
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 b71bfa0..0902a45 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -8,12 +8,11 @@
 import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
 import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
 import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
-import static org.objectweb.asm.Opcodes.V1_6;
-import static org.objectweb.asm.Opcodes.V9;
 
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -198,7 +197,7 @@
     private final ReparseContext context = new ReparseContext();
 
     // DexClass data.
-    private int version;
+    private CfVersion version;
     private boolean deprecated;
     private DexType type;
     private ClassAccessFlags accessFlags;
@@ -238,7 +237,7 @@
     public void visitInnerClass(String name, String outerName, String innerName, int access) {
       if (outerName != null && innerName != null) {
         String separator = DescriptorUtils.computeInnerClassSeparator(outerName, name, innerName);
-        if (separator == null && getMajorVersion() < V9) {
+        if (separator == null && version.isLessThan(CfVersion.V9)) {
           application.options.reporter.info(
               new StringDiagnostic(
                   StringUtils.lines(
@@ -289,43 +288,54 @@
           + name;
     }
 
-    private String illegalClassFilePostfix(int version) {
+    private String illegalClassFilePostfix(CfVersion version) {
       return "Class file version " + version;
     }
 
     private String illegalClassFileMessage(
-        ClassAccessFlags accessFlags, String name, int version, String message) {
+        ClassAccessFlags accessFlags, String name, CfVersion version, String message) {
       return illegalClassFilePrefix(accessFlags, name)
           + " " + message
           + ". " + illegalClassFilePostfix(version) + ".";
     }
 
     @Override
-    public void visit(int version, int access, String name, String signature, String superName,
+    public void visit(
+        int rawVersion,
+        int access,
+        String name,
+        String signature,
+        String superName,
         String[] interfaces) {
-      this.version = version;
-      if (InternalOptions.SUPPORTED_CF_MAJOR_VERSION < getMajorVersion()) {
-        throw new CompilationError("Unsupported class file version: " + getMajorVersion(), origin);
+      version = CfVersion.fromRaw(rawVersion);
+      if (InternalOptions.SUPPORTED_CF_VERSION.isLessThan(version)) {
+        throw new CompilationError("Unsupported class file version: " + version, origin);
       }
       this.deprecated = AsmUtils.isDeprecated(access);
       accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
       type = application.getTypeFromName(name);
       // Check if constraints from
       // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
-      if (!accessFlags.areValid(getMajorVersion(), name.endsWith("/package-info"))) {
+      if (!accessFlags.areValid(version, name.endsWith("/package-info"))) {
         throw new CompilationError(
-            illegalClassFileMessage(accessFlags, name, version,
-                "has invalid access flags. Found: " + accessFlags.toString()), origin);
+            illegalClassFileMessage(
+                accessFlags,
+                name,
+                version,
+                "has invalid access flags. Found: " + accessFlags.toString()),
+            origin);
       }
       if (superName == null && !name.equals(Constants.JAVA_LANG_OBJECT_NAME)) {
         throw new CompilationError(
-            illegalClassFileMessage(accessFlags, name, version,
-                "is missing a super type"), origin);
+            illegalClassFileMessage(accessFlags, name, version, "is missing a super type"), origin);
       }
       if (accessFlags.isInterface()
           && !Objects.equals(superName, Constants.JAVA_LANG_OBJECT_NAME)) {
         throw new CompilationError(
-            illegalClassFileMessage(accessFlags, name, version,
+            illegalClassFileMessage(
+                accessFlags,
+                name,
+                version,
                 "must extend class java.lang.Object. Found: " + Objects.toString(superName)),
             origin);
       }
@@ -448,7 +458,7 @@
       }
       if (enclosingMember == null
           && (clazz.isLocalClass() || clazz.isAnonymousClass())
-          && getMajorVersion() > V1_6) {
+          && CfVersion.V1_6.isLessThan(version)) {
         application.options.warningMissingEnclosingMember(clazz.type, clazz.origin, version);
       }
       if (!clazz.isLibraryClass()) {
@@ -526,14 +536,6 @@
       return annotations;
     }
 
-    private int getMajorVersion() {
-      return version & 0xFFFF;
-    }
-
-    private int getMinorVersion() {
-      return ((version >> 16) & 0xFFFF);
-    }
-
     public boolean isInANest() {
       return !nestMembers.isEmpty() || nestHost != null;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index a1fe5c7..0765824 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
 
 /** Type representing a method definition in the programs compilation unit and its holder. */
 public final class ProgramMethod extends DexClassAndMethod
@@ -81,4 +82,8 @@
     assert holder.isProgramClass();
     return holder.asProgramClass();
   }
+
+  public MethodPosition getPosition() {
+    return getDefinition().getPosition();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index 31ea06f..8a44fd0 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -465,7 +465,8 @@
     if (parameters.isEmpty()) {
       return this;
     }
-    List<ExtraParameter> newExtraParameters = new ArrayList<>();
+    List<ExtraParameter> newExtraParameters =
+        new ArrayList<>(extraParameters.size() + parameters.size());
     newExtraParameters.addAll(extraParameters);
     newExtraParameters.addAll(parameters);
     return new RewrittenPrototypeDescription(
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 f4158c5..cfd5182 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -40,7 +40,6 @@
 public class ClassMerger {
   public static final String CLASS_ID_FIELD_NAME = "$r8$classId";
 
-  private final AppView<AppInfoWithLiveness> appView;
   private final DexProgramClass target;
   private final Collection<DexProgramClass> toMergeGroup;
   private final DexItemFactory dexItemFactory;
@@ -63,7 +62,6 @@
       DexField classIdField,
       Collection<VirtualMethodMerger> virtualMethodMergers,
       Collection<ConstructorMerger> constructorMergers) {
-    this.appView = appView;
     this.lensBuilder = lensBuilder;
     this.mergedClassesBuilder = mergedClassesBuilder;
     this.fieldAccessChangesBuilder = fieldAccessChangesBuilder;
@@ -99,6 +97,10 @@
   }
 
   void merge(DexProgramClass toMerge) {
+    if (!toMerge.isFinal()) {
+      target.getAccessFlags().demoteFromFinal();
+    }
+
     toMerge.forEachProgramDirectMethod(
         method -> {
           DexEncodedMethod definition = method.getDefinition();
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 b6f3058..9f201bf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -139,10 +140,11 @@
     // Tree map as must be sorted.
     Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
 
-    int classFileVersion = -1;
+    CfVersion classFileVersion = null;
     for (DexEncodedMethod constructor : constructors) {
       if (constructor.hasClassFileVersion()) {
-        classFileVersion = Integer.max(classFileVersion, constructor.getClassFileVersion());
+        classFileVersion =
+            CfVersion.maxAllowNull(classFileVersion, constructor.getClassFileVersion());
       }
       DexMethod movedConstructor = moveConstructor(constructor);
       lensBuilder.mapMethod(movedConstructor, movedConstructor);
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 b1d0dbe..c9a40d7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -7,16 +7,20 @@
 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.DontInlinePolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.DontMergeIntoLessVisible;
 import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.IgnoreSynthetics;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAbstractClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassesWithInterfaces;
+import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
 import com.android.tools.r8.horizontalclassmerging.policies.NoFields;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
+import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
 import com.android.tools.r8.horizontalclassmerging.policies.NoRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoStaticClassInitializer;
 import com.android.tools.r8.horizontalclassmerging.policies.NotEntryPoint;
@@ -29,9 +33,9 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.MainDexTracingResult;
+import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
@@ -47,7 +51,7 @@
   public HorizontalClassMerger(
       AppView<AppInfoWithLiveness> appView,
       MainDexTracingResult mainDexTracingResult,
-      ClassMergingEnqueuerExtension classMergingEnqueuerExtension) {
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     this.appView = appView;
     assert appView.options().enableInlining;
 
@@ -59,14 +63,18 @@
             new NoInterfaces(),
             new NoClassesWithInterfaces(),
             new NoAnnotations(),
+            new NoEnums(appView),
             new NoAbstractClasses(),
+            new IgnoreSynthetics(appView),
             new NoClassesOrMembersWithAnnotations(),
             new NoInnerClasses(),
             new NoStaticClassInitializer(),
+            new NoNativeMethods(),
             new NoKeepRules(appView),
             new NotVerticallyMergedIntoSubtype(appView),
-            new NoRuntimeTypeChecks(classMergingEnqueuerExtension),
+            new NoRuntimeTypeChecks(runtimeTypeCheckInfo),
             new NotEntryPoint(appView.dexItemFactory()),
+            new DontInlinePolicy(appView, mainDexTracingResult),
             new PreventMergeIntoMainDex(appView, mainDexTracingResult),
             new SameParentClass(),
             new SameNestHost(),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
new file mode 100644
index 0000000..402ddb1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.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.horizontalclassmerging;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+/**
+ * Calculates the subtyping forrest for all classes. Unlike {@link
+ * com.android.tools.r8.graph.SubtypingInfo}, interfaces are not included in this subtyping
+ * information and only the immediate parents are stored (i.e. the transitive parents are not
+ * calculated). In the following example graph, the roots are A, E and G, and each edge indicates an
+ * entry in {@link SubtypingForrestForClasses#subtypeMap} going from the parent to an entry in the
+ * collection of children. <code>
+ *     A      E     G
+ *    / \     |
+ *   B  C     F
+ *   |
+ *   D
+ * </code>
+ */
+public class SubtypingForrestForClasses {
+  private final AppView<AppInfoWithLiveness> appView;
+
+  private final Collection<DexProgramClass> roots = new ArrayList<>();
+  private final Map<DexProgramClass, Collection<DexProgramClass>> subtypeMap =
+      new IdentityHashMap<>();
+
+  public SubtypingForrestForClasses(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+
+    calculateSubtyping(appView.appInfo().classes());
+  }
+
+  private DexProgramClass superClass(DexProgramClass clazz) {
+    return appView.programDefinitionFor(clazz.superType, clazz);
+  }
+
+  private void calculateSubtyping(Iterable<DexProgramClass> classes) {
+    classes.forEach(this::calculateSubtyping);
+  }
+
+  private void calculateSubtyping(DexProgramClass clazz) {
+    if (clazz.isInterface()) {
+      return;
+    }
+    DexProgramClass superClass = superClass(clazz);
+    if (superClass == null) {
+      roots.add(clazz);
+    } else {
+      subtypeMap.computeIfAbsent(superClass, ignore -> new ArrayList<>()).add(clazz);
+    }
+  }
+
+  public Collection<DexProgramClass> getProgramRoots() {
+    return roots;
+  }
+
+  private Collection<DexProgramClass> getSubtypesFor(DexProgramClass clazz) {
+    return subtypeMap.getOrDefault(clazz, Collections.emptyList());
+  }
+
+  public <T> void traverseNodeDepthFirst(
+      DexProgramClass clazz, T state, BiFunction<DexProgramClass, T, T> consumer) {
+    T newState = consumer.apply(clazz, state);
+    getSubtypesFor(clazz).forEach(subClazz -> traverseNodeDepthFirst(subClazz, newState, consumer));
+  }
+}
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 7f12cc9..b102b69 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -34,7 +34,6 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Consumer;
 
 /**
  * The tree fixer traverses all program classes and finds and fixes references to old classes which
@@ -53,10 +52,6 @@
   private final BiMap<Wrapper<DexMethod>, Wrapper<DexMethod>> reservedInterfaceSignatures =
       HashBiMap.create();
 
-  // Store which methods have been renamed in parent classes.
-  private final Map<DexType, Map<Wrapper<DexMethod>, DexString>> renamedVirtualMethods =
-      new IdentityHashMap<>();
-
   public TreeFixer(
       AppView<AppInfoWithLiveness> appView,
       HorizontallyMergedClasses mergedClasses,
@@ -128,8 +123,13 @@
     Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
     Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
 
-    forEachClassTypeTraverseHierarchy(
-        Iterables.filter(classes, clazz -> !clazz.isInterface()), this::fixupProgramClass);
+    classes.forEach(this::fixupProgramClassSuperType);
+    SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView);
+    // TODO(b/170078037): parallelize this code segment.
+    for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
+      subtypingForrest.traverseNodeDepthFirst(
+          root, new IdentityHashMap<>(), this::fixupProgramClass);
+    }
 
     lensBuilder.remapMethods(movedMethods);
 
@@ -139,14 +139,19 @@
     return lens;
   }
 
-  private void fixupProgramClass(DexProgramClass clazz) {
+  private void fixupProgramClassSuperType(DexProgramClass clazz) {
+    clazz.superType = fixupType(clazz.superType);
+  }
+
+  private Map<Wrapper<DexMethod>, DexString> fixupProgramClass(
+      DexProgramClass clazz, Map<Wrapper<DexMethod>, DexString> remappedVirtualMethods) {
     assert !clazz.isInterface();
 
     // TODO(b/169395592): ensure merged classes have been removed using:
     //   assert !mergedClasses.hasBeenMergedIntoDifferentType(clazz.type);
 
-    Map<Wrapper<DexMethod>, DexString> renamedClassVirtualMethods =
-        new HashMap<>(renamedVirtualMethods.getOrDefault(clazz.superType, new HashMap<>()));
+    Map<Wrapper<DexMethod>, DexString> remappedClassVirtualMethods =
+        new HashMap<>(remappedVirtualMethods);
 
     Set<DexMethod> newDirectMethodReferences = new LinkedHashSet<>();
     Set<DexMethod> newVirtualMethodReferences = new LinkedHashSet<>();
@@ -155,39 +160,16 @@
         .getMethodCollection()
         .replaceVirtualMethods(
             method ->
-                fixupVirtualMethod(renamedClassVirtualMethods, newVirtualMethodReferences, method));
+                fixupVirtualMethod(
+                    remappedClassVirtualMethods, newVirtualMethodReferences, method));
     clazz
         .getMethodCollection()
         .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethodReferences, method));
 
-    if (!renamedClassVirtualMethods.isEmpty()) {
-      renamedVirtualMethods.put(clazz.type, renamedClassVirtualMethods);
-    }
     fixupFields(clazz.staticFields(), clazz::setStaticField);
     fixupFields(clazz.instanceFields(), clazz::setInstanceField);
-  }
 
-  private void traverseUp(
-      DexProgramClass clazz, Set<DexProgramClass> seenClasses, Consumer<DexProgramClass> fn) {
-    if (clazz == null || !seenClasses.add(clazz)) {
-      return;
-    }
-
-    clazz.superType = mergedClasses.getMergeTargetOrDefault(clazz.superType);
-    if (clazz.superType != null) {
-      DexProgramClass superClass = appView.programDefinitionFor(clazz.superType, clazz);
-      traverseUp(superClass, seenClasses, fn);
-    }
-
-    fn.accept(clazz);
-  }
-
-  private void forEachClassTypeTraverseHierarchy(
-      Iterable<DexProgramClass> classes, Consumer<DexProgramClass> fn) {
-    Set<DexProgramClass> seenClasses = Sets.newIdentityHashSet();
-    for (DexProgramClass clazz : classes) {
-      traverseUp(clazz, seenClasses, fn);
-    }
+    return remappedClassVirtualMethods;
   }
 
   private DexEncodedMethod fixupVirtualInterfaceMethod(DexEncodedMethod method) {
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 7342124..ec5cb59 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -64,7 +65,7 @@
           appView
               .withLiveness()
               .appInfo()
-              .resolveMethodOnClass(template, target.superType)
+              .resolveMethodOnClass(template, target.getSuperType())
               .asSingleResolution();
       if (resolutionResult == null) {
         return null;
@@ -72,7 +73,14 @@
       if (resolutionResult.getResolvedMethod().isAbstract()) {
         return null;
       }
-      return resolutionResult.getResolvedMethod().method;
+      if (resolutionResult.getResolvedHolder().isInterface()) {
+        // Ensure that invoke virtual isn't called on an interface method.
+        return resolutionResult
+            .getResolvedMethod()
+            .getReference()
+            .withHolder(target.getSuperType(), appView.dexItemFactory());
+      }
+      return resolutionResult.getResolvedMethod().getReference();
     }
 
     public VirtualMethodMerger build(
@@ -147,11 +155,11 @@
 
     Int2ReferenceSortedMap<DexMethod> classIdToMethodMap = new Int2ReferenceAVLTreeMap<>();
 
-    int classFileVersion = -1;
+    CfVersion classFileVersion = null;
     for (ProgramMethod method : methods) {
       if (method.getDefinition().hasClassFileVersion()) {
-        classFileVersion =
-            Integer.max(classFileVersion, method.getDefinition().getClassFileVersion());
+        CfVersion methodVersion = method.getDefinition().getClassFileVersion();
+        classFileVersion = CfVersion.maxAllowNull(classFileVersion, methodVersion);
       }
       DexMethod newMethod = moveMethod(method);
       lensBuilder.mapMethod(newMethod, newMethod);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
new file mode 100644
index 0000000..0fb2284
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
@@ -0,0 +1,62 @@
+// 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.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexDirectReferenceTracer;
+import com.android.tools.r8.shaking.MainDexTracingResult;
+import com.google.common.collect.Iterables;
+
+public class DontInlinePolicy extends SingleClassPolicy {
+  private final AppView<AppInfoWithLiveness> appView;
+  private final MainDexTracingResult mainDexTracingResult;
+
+  public DontInlinePolicy(
+      AppView<AppInfoWithLiveness> appView, MainDexTracingResult mainDexTracingResult) {
+    this.appView = appView;
+    this.mainDexTracingResult = mainDexTracingResult;
+  }
+
+  private boolean disallowInlining(ProgramMethod method) {
+    Code code = method.getDefinition().getCode();
+
+    // For non-jar/cf code we currently cannot guarantee that markForceInline() will succeed.
+    if (code == null || !code.isCfCode()) {
+      return true;
+    }
+
+    CfCode cfCode = code.asCfCode();
+
+    ConstraintWithTarget constraint =
+        cfCode.computeInliningConstraint(method, appView, appView.graphLens(), method);
+    if (constraint == ConstraintWithTarget.NEVER) {
+      return true;
+    }
+
+    // Constructors can have references beyond the root main dex classes. This can increase the
+    // size of the main dex dependent classes and we should bail out.
+    if (mainDexTracingResult.getRoots().contains(method.getHolderType())
+        && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
+            appView.appInfo(), method, mainDexTracingResult.getRoots())) {
+      return true;
+    }
+
+    return false;
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !Iterables.any(
+        program.directProgramMethods(),
+        method -> method.getDefinition().isInstanceInitializer() && disallowInlining(method));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java
new file mode 100644
index 0000000..78875fd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/IgnoreSynthetics.java
@@ -0,0 +1,24 @@
+// 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.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class IgnoreSynthetics extends SingleClassPolicy {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  public IgnoreSynthetics(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !appView.getSyntheticItems().isSyntheticClass(program);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
new file mode 100644
index 0000000..2da0177
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
@@ -0,0 +1,45 @@
+// 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.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
+
+public class NoEnums extends SingleClassPolicy {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final Reference2BooleanMap<DexClass> cache = new Reference2BooleanOpenHashMap<>();
+
+  public NoEnums(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !program.isEnum() && !isEnumSubtype(program);
+  }
+
+  private boolean isEnumSubtype(DexClass clazz) {
+    if (cache.containsKey(clazz)) {
+      return cache.getBoolean(clazz);
+    }
+    boolean result;
+    if (clazz.type == appView.dexItemFactory().objectType) {
+      result = false;
+    } else if (clazz.type == appView.dexItemFactory().enumType) {
+      result = true;
+    } else {
+      DexClass superClass = appView.definitionFor(clazz.superType);
+      result = superClass != null && isEnumSubtype(superClass);
+    }
+    cache.put(clazz, result);
+    return result;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNativeMethods.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNativeMethods.java
new file mode 100644
index 0000000..f1a278c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNativeMethods.java
@@ -0,0 +1,17 @@
+// 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.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.google.common.collect.Iterables;
+
+public class NoNativeMethods extends SingleClassPolicy {
+  @Override
+  public boolean canMerge(DexProgramClass program) {
+    return !Iterables.any(program.methods(), DexEncodedMethod::isNative);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
index 23f10b2..010fee8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoRuntimeTypeChecks.java
@@ -6,18 +6,18 @@
 
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
+import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 
 public class NoRuntimeTypeChecks extends SingleClassPolicy {
-  private final ClassMergingEnqueuerExtension classMergingEnqueuerExtension;
+  private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
 
-  public NoRuntimeTypeChecks(ClassMergingEnqueuerExtension classMergingEnqueuerExtension) {
-    this.classMergingEnqueuerExtension = classMergingEnqueuerExtension;
+  public NoRuntimeTypeChecks(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+    this.runtimeTypeCheckInfo = runtimeTypeCheckInfo;
   }
 
   @Override
   public boolean canMerge(DexProgramClass clazz) {
     // We currently assume we only merge classes that implement the same set of interfaces.
-    return !classMergingEnqueuerExtension.isRuntimeCheckType(clazz);
+    return !runtimeTypeCheckInfo.isRuntimeCheckType(clazz);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
index e4235e6..ea4040c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -8,12 +8,14 @@
 import static com.google.common.base.Predicates.or;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Phi;
@@ -33,11 +35,13 @@
 public class BasicBlockBehavioralSubsumption {
 
   private final AppView<?> appView;
+  private final IRCode code;
   private final ProgramMethod context;
 
-  public BasicBlockBehavioralSubsumption(AppView<?> appView, ProgramMethod context) {
+  public BasicBlockBehavioralSubsumption(AppView<?> appView, IRCode code) {
     this.appView = appView;
-    this.context = context;
+    this.code = code;
+    this.context = code.context();
   }
 
   public boolean isSubsumedBy(BasicBlock block, BasicBlock other) {
@@ -148,7 +152,19 @@
   }
 
   private boolean isBlockLocalInstructionWithoutSideEffects(Instruction instruction) {
-    return definesBlockLocalValue(instruction) && !instructionMayHaveSideEffects(instruction);
+    if (!definesBlockLocalValue(instruction)) {
+      return false;
+    }
+    if (instruction.instructionMayHaveSideEffects(appView, context)) {
+      return false;
+    }
+    // For constructor calls include field initialization side effects.
+    if (instruction.isInvokeConstructor(appView.dexItemFactory())) {
+      DexEncodedMethod singleTarget =
+          instruction.asInvokeDirect().lookupSingleTarget(appView, context);
+      return singleTarget != null && !singleTarget.getOptimizationInfo().mayHaveSideEffects();
+    }
+    return true;
   }
 
   private boolean definesBlockLocalValue(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index e4bf2a9..fd8dbf7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -15,20 +15,36 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.UnknownInstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DequeUtils;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.ArrayList;
 import java.util.Deque;
 import java.util.IdentityHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
 public abstract class FieldValueAnalysis {
 
+  static class FieldInitializationInfo {
+
+    private final Instruction instruction;
+    private final InstanceFieldInitializationInfo instanceFieldInitializationInfo;
+
+    FieldInitializationInfo(
+        Instruction instruction, InstanceFieldInitializationInfo instanceFieldInitializationInfo) {
+      this.instruction = instruction;
+      this.instanceFieldInitializationInfo = instanceFieldInitializationInfo;
+    }
+  }
+
   final AppView<AppInfoWithLiveness> appView;
   final IRCode code;
   final ProgramMethod context;
@@ -37,7 +53,7 @@
   private DominatorTree dominatorTree;
   private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
 
-  final Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
+  final Map<DexEncodedField, List<FieldInitializationInfo>> putsPerField = new IdentityHashMap<>();
 
   FieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
@@ -61,8 +77,27 @@
     return fieldsMaybeReadBeforeBlockInclusiveCache;
   }
 
+  boolean isInstanceFieldValueAnalysis() {
+    return false;
+  }
+
+  InstanceFieldValueAnalysis asInstanceFieldValueAnalysis() {
+    return null;
+  }
+
   abstract boolean isSubjectToOptimization(DexEncodedField field);
 
+  void recordFieldPut(DexEncodedField field, Instruction instruction) {
+    recordFieldPut(field, instruction, UnknownInstanceFieldInitializationInfo.getInstance());
+  }
+
+  void recordFieldPut(
+      DexEncodedField field, Instruction instruction, InstanceFieldInitializationInfo info) {
+    putsPerField
+        .computeIfAbsent(field, ignore -> new ArrayList<>())
+        .add(new FieldInitializationInfo(instruction, info));
+  }
+
   /** This method analyzes initializers with the purpose of computing field optimization info. */
   void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     AppInfoWithLiveness appInfo = appView.appInfo();
@@ -80,31 +115,42 @@
           DexField field = fieldPut.getField();
           DexEncodedField encodedField = appInfo.resolveField(field).getResolvedField();
           if (encodedField != null && isSubjectToOptimization(encodedField)) {
-            putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
+            recordFieldPut(encodedField, fieldPut);
           }
+        } else if (isInstanceFieldValueAnalysis()
+            && instruction.isInvokeConstructor(appView.dexItemFactory())) {
+          InvokeDirect invoke = instruction.asInvokeDirect();
+          asInstanceFieldValueAnalysis().analyzeForwardingConstructorCall(invoke, code.getThis());
         }
       }
     }
 
     List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
-    for (Entry<DexEncodedField, LinkedList<FieldInstruction>> entry : putsPerField.entrySet()) {
-      DexEncodedField encodedField = entry.getKey();
-      LinkedList<FieldInstruction> fieldPuts = entry.getValue();
+    for (Entry<DexEncodedField, List<FieldInitializationInfo>> entry : putsPerField.entrySet()) {
+      DexEncodedField field = entry.getKey();
+      List<FieldInitializationInfo> fieldPuts = entry.getValue();
       if (fieldPuts.size() > 1) {
         continue;
       }
-      FieldInstruction fieldPut = fieldPuts.getFirst();
+      FieldInitializationInfo info = ListUtils.first(fieldPuts);
+      Instruction instruction = info.instruction;
+      if (instruction.isInvokeDirect()) {
+        asInstanceFieldValueAnalysis()
+            .recordInstanceFieldIsInitializedWithInfo(field, info.instanceFieldInitializationInfo);
+        continue;
+      }
+      FieldInstruction fieldPut = instruction.asFieldInstruction();
       if (!isStraightLineCode) {
         if (!getOrCreateDominatorTree().dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
           continue;
         }
       }
       boolean priorReadsWillReadSameValue =
-          !classInitializerDefaultsResult.hasStaticValue(encodedField) && fieldPut.value().isZero();
-      if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
+          !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
+      if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(field, fieldPut)) {
         continue;
       }
-      updateFieldOptimizationInfo(encodedField, fieldPut, fieldPut.value());
+      updateFieldOptimizationInfo(field, fieldPut, fieldPut.value());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index b818cc2..f508479 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -26,8 +27,10 @@
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.field.EmptyInstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
+import com.android.tools.r8.ir.optimize.info.field.UnknownInstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 
@@ -106,6 +109,16 @@
   }
 
   @Override
+  boolean isInstanceFieldValueAnalysis() {
+    return true;
+  }
+
+  @Override
+  InstanceFieldValueAnalysis asInstanceFieldValueAnalysis() {
+    return this;
+  }
+
+  @Override
   boolean isSubjectToOptimization(DexEncodedField field) {
     return !field.isStatic() && field.holder() == context.getHolderType();
   }
@@ -113,11 +126,45 @@
   @Override
   void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
     if (fieldNeverWrittenBetweenInstancePutAndMethodExit(field, fieldPut.asInstancePut())) {
-      recordFieldIsInitializedWithValue(field, value);
+      recordInstanceFieldIsInitializedWithValue(field, value);
+    }
+  }
+
+  void analyzeForwardingConstructorCall(InvokeDirect invoke, Value thisValue) {
+    if (invoke.getReceiver() != thisValue
+        || invoke.getInvokedMethod().getHolderType() != context.getHolderType()) {
+      // Not a forwarding constructor call.
+      return;
+    }
+
+    ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
+    if (singleTarget == null) {
+      // Failure, should generally not happen.
+      return;
+    }
+
+    InstanceFieldInitializationInfoCollection infos =
+        singleTarget
+            .getDefinition()
+            .getOptimizationInfo()
+            .getInstanceInitializerInfo()
+            .fieldInitializationInfos();
+    for (DexEncodedField field : singleTarget.getHolder().instanceFields()) {
+      assert isSubjectToOptimization(field);
+      InstanceFieldInitializationInfo info = infos.get(field);
+      if (info.isArgumentInitializationInfo()) {
+        int argumentIndex = info.asArgumentInitializationInfo().getArgumentIndex();
+        info = getInstanceFieldInitializationInfo(field, invoke.getArgument(argumentIndex));
+      }
+      recordFieldPut(field, invoke, info);
     }
   }
 
   private void analyzeParentConstructorCall() {
+    if (parentConstructor.getHolderType() == context.getHolderType()) {
+      // Forwarding constructor calls are handled similar to instance-put instructions.
+      return;
+    }
     InstanceFieldInitializationInfoCollection infos =
         parentConstructor
             .getOptimizationInfo()
@@ -129,8 +176,8 @@
           if (fieldNeverWrittenBetweenParentConstructorCallAndMethodExit(field)) {
             if (info.isArgumentInitializationInfo()) {
               int argumentIndex = info.asArgumentInitializationInfo().getArgumentIndex();
-              recordFieldIsInitializedWithValue(
-                  field, parentConstructorCall.arguments().get(argumentIndex));
+              recordInstanceFieldIsInitializedWithValue(
+                  field, parentConstructorCall.getArgument(argumentIndex));
             } else {
               assert info.isSingleValue() || info.isTypeInitializationInfo();
               builder.recordInitializationInfo(field, info);
@@ -139,32 +186,39 @@
         });
   }
 
-  private void recordFieldIsInitializedWithValue(DexEncodedField field, Value value) {
+  private InstanceFieldInitializationInfo getInstanceFieldInitializationInfo(
+      DexEncodedField field, Value value) {
     Value root = value.getAliasedValue();
     if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
       Argument argument = root.definition.asArgument();
-      builder.recordInitializationInfo(
-          field, factory.createArgumentInitializationInfo(argument.getIndex()));
-      return;
+      return factory.createArgumentInitializationInfo(argument.getIndex());
     }
-
     AbstractValue abstractValue = value.getAbstractValue(appView, context);
     if (abstractValue.isSingleValue()) {
-      builder.recordInitializationInfo(field, abstractValue.asSingleValue());
-      return;
+      return abstractValue.asSingleValue();
     }
-
-    DexType fieldType = field.field.type;
+    DexType fieldType = field.type();
     if (fieldType.isClassType()) {
       ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
       TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
       TypeElement staticFieldType = TypeElement.fromDexType(fieldType, maybeNull(), appView);
       if (dynamicLowerBoundType != null || !dynamicUpperBoundType.equals(staticFieldType)) {
-        builder.recordInitializationInfo(
-            field,
-            factory.createTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType));
+        return factory.createTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType);
       }
     }
+    return UnknownInstanceFieldInitializationInfo.getInstance();
+  }
+
+  void recordInstanceFieldIsInitializedWithInfo(
+      DexEncodedField field, InstanceFieldInitializationInfo info) {
+    if (!info.isUnknown()) {
+      builder.recordInitializationInfo(field, info);
+    }
+  }
+
+  void recordInstanceFieldIsInitializedWithValue(DexEncodedField field, Value value) {
+    recordInstanceFieldIsInitializedWithInfo(
+        field, getInstanceFieldInitializationInfo(field, value));
   }
 
   private boolean fieldNeverWrittenBetweenInstancePutAndMethodExit(
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 6722cfb..17b49b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -157,7 +157,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forCheckCast(type, context.getHolder());
+    return inliningConstraints.forCheckCast(type, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 86101ba..7f80abf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -158,7 +158,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forConstClass(clazz, context.getHolder());
+    return inliningConstraints.forConstClass(clazz, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 0753447..2c7313e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -160,7 +160,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forDexItemBasedConstString(item, context.getHolder());
+    return inliningConstraints.forDexItemBasedConstString(item, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 22e4c4e..61a1882 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -158,7 +158,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInitClass(clazz, context.getHolder());
+    return inliningConstraints.forInitClass(clazz, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 7dad747..090812b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -144,7 +144,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInstanceGet(getField(), context.getHolder());
+    return inliningConstraints.forInstanceGet(getField(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 23808f4..0530403 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -88,7 +88,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInstanceOf(type, context.getHolder());
+    return inliningConstraints.forInstanceOf(type, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 296b2f9..c38c5c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -184,7 +184,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInstancePut(getField(), context.getHolder());
+    return inliningConstraints.forInstancePut(getField(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 6a42c80..62c94b8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -1240,6 +1241,10 @@
     return null;
   }
 
+  public boolean isInvokeConstructor(DexItemFactory dexItemFactory) {
+    return false;
+  }
+
   public boolean isInvokeDirect() {
     return false;
   }
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 6d452e1..2a81647 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+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.ProgramMethod;
@@ -109,6 +110,11 @@
   }
 
   @Override
+  public boolean isInvokeConstructor(DexItemFactory dexItemFactory) {
+    return getInvokedMethod().isInstanceInitializer(dexItemFactory);
+  }
+
+  @Override
   public boolean isInvokeDirect() {
     return true;
   }
@@ -140,7 +146,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokeDirect(getInvokedMethod(), context.getHolder());
+    return inliningConstraints.forInvokeDirect(getInvokedMethod(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index e95be77..83acc1d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -119,7 +119,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokeInterface(getInvokedMethod(), context.getHolder());
+    return inliningConstraints.forInvokeInterface(getInvokedMethod(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 063592f..7163fdd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -78,7 +78,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokeMultiNewArray(type, context.getHolder());
+    return inliningConstraints.forInvokeMultiNewArray(type, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 25a4b47..cad5e1d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -107,7 +107,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokeNewArray(type, context.getHolder());
+    return inliningConstraints.forInvokeNewArray(type, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 824a283..dbcafdc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -140,7 +140,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokePolymorphic(getInvokedMethod(), context.getHolder());
+    return inliningConstraints.forInvokePolymorphic(getInvokedMethod(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index e89d9b0..696d674 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -130,7 +130,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokeStatic(getInvokedMethod(), context.getHolder());
+    return inliningConstraints.forInvokeStatic(getInvokedMethod(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 4815431..34fa59c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -120,7 +120,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokeSuper(getInvokedMethod(), context.getHolder());
+    return inliningConstraints.forInvokeSuper(getInvokedMethod(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 951a451..e7fb163 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -142,7 +142,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forInvokeVirtual(getInvokedMethod(), context.getHolder());
+    return inliningConstraints.forInvokeVirtual(getInvokedMethod(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index eb12b5a..37fbdf0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -113,7 +113,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forNewArrayEmpty(type, context.getHolder());
+    return inliningConstraints.forNewArrayEmpty(type, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 0c6f609..704de30 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -103,7 +103,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forNewInstance(clazz, context.getHolder());
+    return inliningConstraints.forNewInstance(clazz, context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 2a5ffbd..8a3f867 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -170,7 +170,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forStaticGet(getField(), context.getHolder());
+    return inliningConstraints.forStaticGet(getField(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index e268cbe..9df14cc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -180,7 +180,7 @@
   @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context) {
-    return inliningConstraints.forStaticPut(getField(), context.getHolder());
+    return inliningConstraints.forStaticPut(getField(), context);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 8f2c15e..74191f0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -14,9 +14,9 @@
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getNull;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getSingle;
 import static com.android.tools.r8.ir.analysis.type.TypeElement.getWide;
-import static org.objectweb.asm.Opcodes.V1_8;
 
 import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -2210,7 +2210,7 @@
                     local,
                     readType);
           } else {
-            assert method.getDefinition().getClassFileVersion() < V1_8;
+            assert method.getDefinition().getClassFileVersion().isLessThan(CfVersion.V1_8);
             hasIncorrectStackMapTypes = true;
           }
         }
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 b7964c8..c271af1 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
@@ -269,10 +269,13 @@
             ? new InterfaceMethodRewriter(appView, this)
             : null;
     this.twrCloseResourceRewriter =
-        ((options.desugarState == DesugarState.ON) && enableTwrCloseResourceDesugaring())
+        (options.desugarState == DesugarState.ON && enableTwrCloseResourceDesugaring())
             ? new TwrCloseResourceRewriter(appView, this)
             : null;
-    this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
+    this.backportedMethodRewriter =
+        (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
+            ? new BackportedMethodRewriter(appView)
+            : null;
     this.desugaredLibraryRetargeter =
         options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
             ? null
@@ -655,7 +658,8 @@
     }
 
     // Process the application identifying outlining candidates.
-    GraphLens graphLensForIR = appView.graphLens();
+    GraphLens initialGraphLensForIR = appView.graphLens();
+    GraphLens graphLensForIR = initialGraphLensForIR;
     OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
     PostMethodProcessor.Builder postMethodProcessorBuilder =
         new PostMethodProcessor.Builder(getOptimizationsForPostIRProcessing());
@@ -759,7 +763,7 @@
 
     printPhase("Lambda merging finalization");
     // TODO(b/127694949): Adapt to PostOptimization.
-    finalizeLambdaMerging(application, feedback, builder, executorService);
+    finalizeLambdaMerging(application, feedback, builder, executorService, initialGraphLensForIR);
 
     printPhase("Desugared library API Conversion finalization");
     generateDesugaredLibraryAPIWrappers(builder, executorService);
@@ -934,11 +938,12 @@
       DexApplication application,
       OptimizationFeedback feedback,
       Builder<?> builder,
-      ExecutorService executorService)
+      ExecutorService executorService,
+      GraphLens appliedGraphLens)
       throws ExecutionException {
     if (lambdaMerger != null) {
       lambdaMerger.applyLambdaClassMapping(
-          application, this, feedback, builder, executorService);
+          application, this, feedback, builder, executorService, appliedGraphLens);
     } else {
       appView.setHorizontallyMergedLambdaClasses(HorizontallyMergedLambdaClasses.empty());
     }
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 8b827d3..0512e69 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
@@ -32,16 +32,20 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexEncodedField;
 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.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.FieldLookupResult;
 import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
@@ -374,7 +378,7 @@
             {
               InstanceGet instanceGet = current.asInstanceGet();
               DexField field = instanceGet.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupGetFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -399,7 +403,7 @@
             {
               InstancePut instancePut = current.asInstancePut();
               DexField field = instancePut.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupPutFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -421,7 +425,7 @@
             {
               StaticGet staticGet = current.asStaticGet();
               DexField field = staticGet.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupGetFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -445,7 +449,7 @@
             {
               StaticPut staticPut = current.asStaticPut();
               DexField field = staticPut.getField();
-              DexField actualField = graphLens.lookupField(field);
+              DexField actualField = rewriteFieldReference(field, method, graphLens);
               DexMethod replacementMethod =
                   graphLens.lookupPutFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
@@ -581,6 +585,22 @@
     assert code.hasNoVerticallyMergedClasses(appView);
   }
 
+  private DexField rewriteFieldReference(
+      DexField reference, ProgramMethod context, GraphLens graphLens) {
+    FieldLookupResult lookup = graphLens.lookupFieldResult(reference);
+    if (lookup.hasReboundReference()) {
+      DexClass holder = appView.definitionFor(lookup.getReboundReference().getHolderType());
+      DexEncodedField definition = lookup.getReboundReference().lookupOnClass(holder);
+      if (definition != null) {
+        DexClassAndField field = DexClassAndField.create(holder, definition);
+        if (AccessControl.isMemberAccessible(field, holder, context, appView).isTrue()) {
+          return lookup.getReboundReference();
+        }
+      }
+    }
+    return lookup.getReference();
+  }
+
   // If the initialValue is a default value and its type is rewritten from a reference type to a
   // primitive type, then the default value type lattice needs to be changed.
   private Value rewriteValueIfDefault(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index b45fe42..04fb914 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -106,9 +107,25 @@
   }
 
   public boolean desugar(ProgramMethod method, AppInfoWithClassHierarchy appInfo) {
+    return desugar(method, appInfo, synthesizedMethods::add);
+  }
+
+  public boolean desugar(
+      ProgramMethod method, AppInfoWithClassHierarchy appInfo, Consumer<ProgramMethod> consumer) {
     if (!enabled) {
       return false;
     }
+    if (method.getDefinition().getCode().isDexCode()) {
+      appView
+          .options()
+          .reporter
+          .error(
+              new StringDiagnostic(
+                  "Unsupported attempt to desugar DEX code",
+                  method.getOrigin(),
+                  method.getPosition()));
+      return false;
+    }
     CfCode code = method.getDefinition().getCode().asCfCode();
     ListIterator<CfInstruction> iterator = code.getInstructions().listIterator();
     boolean replaced = false;
@@ -127,8 +144,7 @@
           iterator = mutableInstructions.listIterator(iterator.previousIndex());
           iterator.next();
         }
-        provider.rewriteInvoke(
-            invoke, iterator, method.getHolder(), appInfo, synthesizedMethods::add);
+        provider.rewriteInvoke(invoke, iterator, method.getHolder(), appInfo, consumer);
         replaced = true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index f2ae9a1..758830d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -16,6 +16,7 @@
 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.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -29,9 +30,12 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
@@ -79,6 +83,12 @@
     boolean isEmpty() {
       return signatures.isEmpty();
     }
+
+    public MethodSignatures withoutAll(MethodSignatures other) {
+      Set<Wrapper<DexMethod>> merged = new HashSet<>(signatures);
+      merged.removeAll(other.signatures);
+      return signatures.size() == merged.size() ? this : new MethodSignatures(merged);
+    }
   }
 
   // Collection of information known at the point of a given (non-library) class.
@@ -86,7 +96,8 @@
   // class hierarchy. Thus, in the case of additions the parent pointer will contain prior info.
   private static class ClassInfo {
 
-    static final ClassInfo EMPTY = new ClassInfo(null, ImmutableList.of());
+    static final ClassInfo EMPTY =
+        new ClassInfo(null, ImmutableList.of(), EmulatedInterfaceInfo.EMPTY);
 
     final ClassInfo parent;
 
@@ -94,17 +105,26 @@
     // class hierarchy. This set consists of the default interface methods, i.e., the targets of the
     // forwarding methods, *not* the forwarding methods themselves.
     final ImmutableList<DexEncodedMethod> forwardedMethodTargets;
+    // If the forwarding methods for the emulated interface methods have not been added yet,
+    // this contains the information to add it in the subclasses.
+    final EmulatedInterfaceInfo emulatedInterfaceInfo;
 
-    ClassInfo(ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+    ClassInfo(
+        ClassInfo parent,
+        ImmutableList<DexEncodedMethod> forwardedMethodTargets,
+        EmulatedInterfaceInfo emulatedInterfaceInfo) {
       this.parent = parent;
       this.forwardedMethodTargets = forwardedMethodTargets;
+      this.emulatedInterfaceInfo = emulatedInterfaceInfo;
     }
 
     static ClassInfo create(
-        ClassInfo parent, ImmutableList<DexEncodedMethod> forwardedMethodTargets) {
+        ClassInfo parent,
+        ImmutableList<DexEncodedMethod> forwardedMethodTargets,
+        EmulatedInterfaceInfo emulatedInterfaceInfo) {
       return forwardedMethodTargets.isEmpty()
           ? parent
-          : new ClassInfo(parent, forwardedMethodTargets);
+          : new ClassInfo(parent, forwardedMethodTargets, emulatedInterfaceInfo);
     }
 
     public boolean isEmpty() {
@@ -117,6 +137,107 @@
     }
   }
 
+  // Collection of information on what signatures and what emulated interfaces require
+  // forwarding methods for library classes and interfaces.
+  private static class SignaturesInfo {
+
+    static final SignaturesInfo EMPTY =
+        new SignaturesInfo(MethodSignatures.EMPTY, EmulatedInterfaceInfo.EMPTY);
+
+    final MethodSignatures signatures;
+    final EmulatedInterfaceInfo emulatedInterfaceInfo;
+
+    private SignaturesInfo(
+        MethodSignatures methodsToForward, EmulatedInterfaceInfo emulatedInterfaceInfo) {
+      this.signatures = methodsToForward;
+      this.emulatedInterfaceInfo = emulatedInterfaceInfo;
+    }
+
+    public static SignaturesInfo create(MethodSignatures signatures) {
+      if (signatures.isEmpty()) {
+        return EMPTY;
+      }
+      return new SignaturesInfo(signatures, EmulatedInterfaceInfo.EMPTY);
+    }
+
+    public SignaturesInfo merge(SignaturesInfo other) {
+      if (isEmpty()) {
+        return other;
+      }
+      if (other.isEmpty()) {
+        return this;
+      }
+      return new SignaturesInfo(
+          signatures.merge(other.signatures),
+          emulatedInterfaceInfo.merge(other.emulatedInterfaceInfo));
+    }
+
+    public MethodSignatures emulatedInterfaceSignaturesToForward() {
+      return emulatedInterfaceInfo.signatures.withoutAll(signatures);
+    }
+
+    boolean isEmpty() {
+      return signatures.isEmpty() && emulatedInterfaceInfo.isEmpty();
+    }
+
+    public SignaturesInfo withSignatures(MethodSignatures additions) {
+      if (additions.isEmpty()) {
+        return this;
+      }
+      MethodSignatures newSignatures = signatures.merge(additions);
+      return new SignaturesInfo(newSignatures, emulatedInterfaceInfo);
+    }
+
+    public SignaturesInfo withEmulatedInterfaceInfo(
+        EmulatedInterfaceInfo additionalEmulatedInterfaceInfo) {
+      if (additionalEmulatedInterfaceInfo.isEmpty()) {
+        return this;
+      }
+      return new SignaturesInfo(
+          signatures, emulatedInterfaceInfo.merge(additionalEmulatedInterfaceInfo));
+    }
+  }
+
+  // List of emulated interfaces and corresponding signatures which may require forwarding methods.
+  // If one of the signatures has an override, then the class holding the override is required to
+  // add the forwarding methods for all signatures, and introduce the corresponding emulated
+  // interface in its interfaces attribute for correct emulated dispatch.
+  // If no override is present, then no forwarding methods are required, the class relies on the
+  // default behavior of the emulated dispatch.
+  private static class EmulatedInterfaceInfo {
+
+    static final EmulatedInterfaceInfo EMPTY =
+        new EmulatedInterfaceInfo(MethodSignatures.EMPTY, ImmutableSet.of());
+
+    final MethodSignatures signatures;
+    final ImmutableSet<DexType> emulatedInterfaces;
+
+    private EmulatedInterfaceInfo(
+        MethodSignatures methodsToForward, ImmutableSet<DexType> emulatedInterfaces) {
+      this.signatures = methodsToForward;
+      this.emulatedInterfaces = emulatedInterfaces;
+    }
+
+    public EmulatedInterfaceInfo merge(EmulatedInterfaceInfo other) {
+      if (isEmpty()) {
+        return other;
+      }
+      if (other.isEmpty()) {
+        return this;
+      }
+      ImmutableSet.Builder<DexType> newEmulatedInterfaces = ImmutableSet.builder();
+      newEmulatedInterfaces.addAll(emulatedInterfaces);
+      newEmulatedInterfaces.addAll(other.emulatedInterfaces);
+      return new EmulatedInterfaceInfo(
+          signatures.merge(other.signatures), newEmulatedInterfaces.build());
+    }
+
+    public boolean isEmpty() {
+      assert !emulatedInterfaces.isEmpty() || signatures.isEmpty();
+      return emulatedInterfaces.isEmpty();
+    }
+  }
+
   // Helper to keep track of the direct active subclass and nearest program subclass for reporting.
   private static class ReportingContext {
 
@@ -181,10 +302,10 @@
   private final Map<DexClass, ClassInfo> classInfo = new IdentityHashMap<>();
 
   // Mapping from library classes to their information summary.
-  private final Map<DexLibraryClass, MethodSignatures> libraryClassInfo = new IdentityHashMap<>();
+  private final Map<DexLibraryClass, SignaturesInfo> libraryClassInfo = new IdentityHashMap<>();
 
   // Mapping from arbitrary interfaces to an information summary.
-  private final Map<DexClass, MethodSignatures> interfaceInfo = new IdentityHashMap<>();
+  private final Map<DexClass, SignaturesInfo> interfaceInfo = new IdentityHashMap<>();
 
   // Mapping from actual program classes to the synthesized forwarding methods to be created.
   private final Map<DexProgramClass, ProgramMethodSet> newSyntheticMethods =
@@ -228,35 +349,113 @@
   }
 
   // Computes the set of method signatures that may need forwarding methods on derived classes.
-  private MethodSignatures computeInterfaceInfo(DexClass iface, MethodSignatures signatures) {
+  private SignaturesInfo computeInterfaceInfo(DexClass iface, SignaturesInfo interfaceInfo) {
     assert iface.isInterface();
     assert iface.superType == dexItemFactory.objectType;
+    assert !rewriter.isEmulatedInterface(iface.type);
     // Add non-library default methods as well as those for desugared library classes.
     if (!iface.isLibraryClass() || (needsLibraryInfo() && rewriter.isInDesugaredLibrary(iface))) {
-      Set<Wrapper<DexMethod>> additions =
-          new HashSet<>(iface.getMethodCollection().numberOfVirtualMethods());
-      for (DexEncodedMethod method : iface.virtualMethods(DexEncodedMethod::isDefaultMethod)) {
-        additions.add(equivalence.wrap(method.method));
-      }
-      if (!additions.isEmpty()) {
-        signatures = signatures.merge(MethodSignatures.create(additions));
-      }
+      MethodSignatures signatures = getDefaultMethods(iface);
+      interfaceInfo = interfaceInfo.withSignatures(signatures);
     }
-    return signatures;
+    return interfaceInfo;
+  }
+
+  private SignaturesInfo computeEmulatedInterfaceInfo(
+      DexClass iface, SignaturesInfo interfaceInfo) {
+    assert iface.isInterface();
+    assert iface.superType == dexItemFactory.objectType;
+    assert rewriter.isEmulatedInterface(iface.type);
+    assert needsLibraryInfo();
+    MethodSignatures signatures = getDefaultMethods(iface);
+    EmulatedInterfaceInfo emulatedInterfaceInfo =
+        new EmulatedInterfaceInfo(signatures, ImmutableSet.of(iface.type));
+    return interfaceInfo.withEmulatedInterfaceInfo(emulatedInterfaceInfo);
+  }
+
+  private MethodSignatures getDefaultMethods(DexClass iface) {
+    assert iface.isInterface();
+    Set<Wrapper<DexMethod>> defaultMethods =
+        new HashSet<>(iface.getMethodCollection().numberOfVirtualMethods());
+    for (DexEncodedMethod method : iface.virtualMethods(DexEncodedMethod::isDefaultMethod)) {
+      defaultMethods.add(equivalence.wrap(method.method));
+    }
+    return MethodSignatures.create(defaultMethods);
   }
 
   // Computes the set of signatures of that may need forwarding methods on classes that derive
   // from a library class.
-  private MethodSignatures computeLibraryClassInfo(
-      DexLibraryClass clazz, MethodSignatures signatures) {
+  private SignaturesInfo computeLibraryClassInfo(DexLibraryClass clazz, SignaturesInfo signatures) {
     // The result is the identity as the library class does not itself contribute to the set.
     return signatures;
   }
 
   // The computation of a class information and the insertions of forwarding methods.
   private ClassInfo computeClassInfo(
-      DexClass clazz, ClassInfo superInfo, MethodSignatures signatures) {
+      DexClass clazz, ClassInfo superInfo, SignaturesInfo signatureInfo) {
     Builder<DexEncodedMethod> additionalForwards = ImmutableList.builder();
+    // First we deal with non-emulated interface desugaring.
+    resolveForwardingMethods(clazz, superInfo, signatureInfo.signatures, additionalForwards);
+    // Second we deal with emulated interface, if one method has override in the current class,
+    // we resolve them, else we propagate the emulated interface info down.
+    if (shouldResolveForwardingMethodsForEmulatedInterfaces(
+        clazz, signatureInfo.emulatedInterfaceInfo)) {
+      resolveForwardingMethods(
+          clazz,
+          superInfo,
+          signatureInfo.emulatedInterfaceSignaturesToForward(),
+          additionalForwards);
+      duplicateEmulatedInterfaces(clazz, signatureInfo.emulatedInterfaceInfo.emulatedInterfaces);
+      return ClassInfo.create(superInfo, additionalForwards.build(), EmulatedInterfaceInfo.EMPTY);
+    }
+    return ClassInfo.create(
+        superInfo, additionalForwards.build(), signatureInfo.emulatedInterfaceInfo);
+  }
+
+  // All classes implementing an emulated interface and overriding a default method should now
+  // implement the interface and the emulated one for correct emulated dispatch.
+  // The class signature won't include the correct type parameters for the duplicated interfaces,
+  // i.e., there will be foo.A instead of foo.A<K,V>, but such parameters are unused.
+  private void duplicateEmulatedInterfaces(
+      DexClass clazz, ImmutableSet<DexType> emulatedInterfaces) {
+    if (clazz.isNotProgramClass()) {
+      return;
+    }
+    // We need to introduce them in deterministic order for deterministic compilation.
+    ArrayList<DexType> sortedEmulatedInterfaces = new ArrayList<>(emulatedInterfaces);
+    Collections.sort(sortedEmulatedInterfaces, DexType::slowCompareTo);
+    List<GenericSignature.ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>();
+    for (DexType extraInterface : sortedEmulatedInterfaces) {
+      extraInterfaceSignatures.add(
+          new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(extraInterface)));
+    }
+    clazz.asProgramClass().addExtraInterfaces(extraInterfaceSignatures);
+  }
+
+  // If any of the signature would lead to a different behavior than the default method on the
+  // emulated interface, we need to resolve the forwarding methods.
+  private boolean shouldResolveForwardingMethodsForEmulatedInterfaces(
+      DexClass clazz, EmulatedInterfaceInfo emulatedInterfaceInfo) {
+    AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+    for (Wrapper<DexMethod> signature : emulatedInterfaceInfo.signatures.signatures) {
+      ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(signature.get(), clazz);
+      if (resolutionResult.isFailedResolution()) {
+        return true;
+      }
+      DexClass resolvedHolder = resolutionResult.asSingleResolution().getResolvedHolder();
+      if (!resolvedHolder.isLibraryClass()
+          && !emulatedInterfaceInfo.emulatedInterfaces.contains(resolvedHolder.type)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void resolveForwardingMethods(
+      DexClass clazz,
+      ClassInfo superInfo,
+      MethodSignatures signatures,
+      Builder<DexEncodedMethod> additionalForwards) {
     for (Wrapper<DexMethod> wrapper : signatures.signatures) {
       resolveForwardForSignature(
           clazz,
@@ -268,7 +467,6 @@
             }
           });
     }
-    return ClassInfo.create(superInfo, additionalForwards.build());
   }
 
   // Looks up a method signature from the point of 'clazz', if it can dispatch to a default method
@@ -439,7 +637,10 @@
     // a library class or is not, but cannot be both.
     ReportingContext thisContext = context.forClass(clazz);
     ClassInfo superInfo = visitClassInfo(clazz.superType, thisContext);
-    MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+    SignaturesInfo signatures = visitLibraryClassInfo(clazz.superType);
+    // The class may inherit emulated interface info from its program superclass if the latter
+    // did not require to resolve the forwarding methods for emualted interfaces.
+    signatures = signatures.withEmulatedInterfaceInfo(superInfo.emulatedInterfaceInfo);
     assert superInfo.isEmpty() || signatures.isEmpty();
     for (DexType iface : clazz.interfaces.values) {
       signatures = signatures.merge(visitInterfaceInfo(iface, thisContext));
@@ -447,24 +648,24 @@
     return computeClassInfo(clazz, superInfo, signatures);
   }
 
-  private MethodSignatures visitLibraryClassInfo(DexType type) {
+  private SignaturesInfo visitLibraryClassInfo(DexType type) {
     // No desugaring required, no library class analysis.
     if (ignoreLibraryInfo()) {
-      return MethodSignatures.EMPTY;
+      return SignaturesInfo.EMPTY;
     }
     DexClass clazz = definitionOrNull(type, LibraryReportingContext.LIBRARY_CONTEXT);
-    return clazz == null ? MethodSignatures.EMPTY : visitLibraryClassInfo(clazz);
+    return clazz == null ? SignaturesInfo.EMPTY : visitLibraryClassInfo(clazz);
   }
 
-  private MethodSignatures visitLibraryClassInfo(DexClass clazz) {
+  private SignaturesInfo visitLibraryClassInfo(DexClass clazz) {
     assert !clazz.isInterface();
     return clazz.isLibraryClass()
         ? libraryClassInfo.computeIfAbsent(clazz.asLibraryClass(), this::visitLibraryClassInfoRaw)
-        : MethodSignatures.EMPTY;
+        : SignaturesInfo.EMPTY;
   }
 
-  private MethodSignatures visitLibraryClassInfoRaw(DexLibraryClass clazz) {
-    MethodSignatures signatures = visitLibraryClassInfo(clazz.superType);
+  private SignaturesInfo visitLibraryClassInfoRaw(DexLibraryClass clazz) {
+    SignaturesInfo signatures = visitLibraryClassInfo(clazz.superType);
     for (DexType iface : clazz.interfaces.values) {
       signatures =
           signatures.merge(visitInterfaceInfo(iface, LibraryReportingContext.LIBRARY_CONTEXT));
@@ -472,24 +673,26 @@
     return computeLibraryClassInfo(clazz, signatures);
   }
 
-  private MethodSignatures visitInterfaceInfo(DexType iface, ReportingContext context) {
+  private SignaturesInfo visitInterfaceInfo(DexType iface, ReportingContext context) {
     DexClass definition = definitionOrNull(iface, context);
-    return definition == null ? MethodSignatures.EMPTY : visitInterfaceInfo(definition, context);
+    return definition == null ? SignaturesInfo.EMPTY : visitInterfaceInfo(definition, context);
   }
 
-  private MethodSignatures visitInterfaceInfo(DexClass iface, ReportingContext context) {
+  private SignaturesInfo visitInterfaceInfo(DexClass iface, ReportingContext context) {
     if (iface.isLibraryClass() && ignoreLibraryInfo()) {
-      return MethodSignatures.EMPTY;
+      return SignaturesInfo.EMPTY;
     }
     return interfaceInfo.computeIfAbsent(iface, key -> visitInterfaceInfoRaw(key, context));
   }
 
-  private MethodSignatures visitInterfaceInfoRaw(DexClass iface, ReportingContext context) {
+  private SignaturesInfo visitInterfaceInfoRaw(DexClass iface, ReportingContext context) {
     ReportingContext thisContext = context.forClass(iface);
-    MethodSignatures signatures = MethodSignatures.EMPTY;
+    SignaturesInfo interfaceInfo = SignaturesInfo.EMPTY;
     for (DexType superiface : iface.interfaces.values) {
-      signatures = signatures.merge(visitInterfaceInfo(superiface, thisContext));
+      interfaceInfo = interfaceInfo.merge(visitInterfaceInfo(superiface, thisContext));
     }
-    return computeInterfaceInfo(iface, signatures);
+    return rewriter.isEmulatedInterface(iface.type)
+        ? computeEmulatedInterfaceInfo(iface, interfaceInfo)
+        : computeInterfaceInfo(iface, interfaceInfo);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java
deleted file mode 100644
index 6f72091..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryEmulatedInterfaceDuplicator.java
+++ /dev/null
@@ -1,124 +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.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class DesugaredLibraryEmulatedInterfaceDuplicator {
-
-  final AppView<?> appView;
-  final Map<DexType, DexType> emulatedInterfaces;
-
-  public DesugaredLibraryEmulatedInterfaceDuplicator(AppView<?> appView) {
-    this.appView = appView;
-    emulatedInterfaces =
-        appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface();
-  }
-
-  public void duplicateEmulatedInterfaces() {
-    // All classes implementing an emulated interface now implements the interface and the
-    // emulated one, as well as hidden overrides, for correct emulated dispatch.
-    // We not that duplicated interfaces won't feature the correct type parameters in the
-    // class signature since such signature is expected to be unused.
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (clazz.type == appView.dexItemFactory().objectType) {
-        continue;
-      }
-      if (emulatedInterfaces.containsKey(clazz.type)) {
-        transformEmulatedInterfaces(clazz);
-      } else {
-        duplicateEmulatedInterfaces(clazz);
-      }
-    }
-  }
-
-  private void transformEmulatedInterfaces(DexProgramClass clazz) {
-    List<ClassTypeSignature> newInterfaces = new ArrayList<>();
-    GenericSignature.ClassSignature classSignature = clazz.getClassSignature();
-    for (int i = 0; i < clazz.interfaces.size(); i++) {
-      DexType itf = clazz.interfaces.values[i];
-      assert emulatedInterfaces.containsKey(itf);
-      List<FieldTypeSignature> typeArguments;
-      if (classSignature == null) {
-        typeArguments = Collections.emptyList();
-      } else {
-        ClassTypeSignature classTypeSignature = classSignature.superInterfaceSignatures().get(i);
-        assert itf == classTypeSignature.type();
-        typeArguments = classTypeSignature.typeArguments();
-      }
-      newInterfaces.add(new ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
-    }
-    clazz.replaceInterfaces(newInterfaces);
-  }
-
-  private void duplicateEmulatedInterfaces(DexProgramClass clazz) {
-    List<DexType> extraInterfaces = new ArrayList<>();
-    LinkedList<DexClass> workList = new LinkedList<>();
-    Set<DexType> processed = Sets.newIdentityHashSet();
-    workList.add(clazz);
-    while (!workList.isEmpty()) {
-      DexClass dexClass = workList.removeFirst();
-      if (processed.contains(dexClass.type)) {
-        continue;
-      }
-      processed.add(dexClass.type);
-      if (dexClass.superType != appView.dexItemFactory().objectType) {
-        processSuperType(clazz.superType, extraInterfaces, workList);
-      }
-      for (DexType itf : dexClass.interfaces) {
-        processSuperType(itf, extraInterfaces, workList);
-      }
-    }
-    extraInterfaces = removeDuplicates(extraInterfaces);
-    List<ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>();
-    for (DexType extraInterface : extraInterfaces) {
-      extraInterfaceSignatures.add(new ClassTypeSignature(extraInterface));
-    }
-    clazz.addExtraInterfaces(extraInterfaceSignatures);
-  }
-
-  private List<DexType> removeDuplicates(List<DexType> extraInterfaces) {
-    if (extraInterfaces.size() <= 1) {
-      return extraInterfaces;
-    }
-    // TODO(b/161399032): It would be nice to remove duplicate based on inheritance, i.e.,
-    //  if there is ConcurrentMap<K,V> and Map<K,V>, Map<K,V> can be removed.
-    return new ArrayList<>(new HashSet<>(extraInterfaces));
-  }
-
-  void processSuperType(
-      DexType superType, List<DexType> extraInterfaces, LinkedList<DexClass> workList) {
-    if (emulatedInterfaces.containsKey(superType)) {
-      extraInterfaces.add(emulatedInterfaces.get(superType));
-    } else {
-      DexClass superClass = appView.definitionFor(superType);
-      if (shouldProcessSuperclass(superClass)) {
-        workList.add(superClass);
-      }
-    }
-  }
-
-  private boolean shouldProcessSuperclass(DexClass superclazz) {
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      return false;
-    }
-    // TODO(b/161399032): Pay-as-you-go design: stop duplication on library boundaries.
-    return superclazz != null && superclazz.isLibraryClass();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 61a5c56..ee05401 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -26,8 +26,10 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -207,6 +209,10 @@
     return emulatedInterfaces.containsKey(itf);
   }
 
+  DexType getEmulatedInterface(DexType itf) {
+    return emulatedInterfaces.get(itf);
+  }
+
   // Rewrites the references to static and default interface methods.
   // NOTE: can be called for different methods concurrently.
   public void rewriteMethodReferences(DexEncodedMethod encodedMethod, IRCode code) {
@@ -430,8 +436,19 @@
           DexMethod invokedMethod = invokeMethod.getInvokedMethod();
           DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
           if (dexType != null) {
-            rewriteCurrentInstructionToEmulatedInterfaceCall(
-                dexType, invokedMethod, invokeMethod, instructions);
+            // The call potentially ends up in a library class, in which case we need to rewrite,
+            // since the code may be in the desugared library.
+            SingleResolutionResult resolution =
+                appView
+                    .appInfoForDesugaring()
+                    .resolveMethod(invokedMethod, invokeMethod.getInterfaceBit())
+                    .asSingleResolution();
+            if (resolution != null
+                && (resolution.getResolvedHolder().isLibraryClass()
+                    || appView.options().isDesugaredLibraryCompilation())) {
+              rewriteCurrentInstructionToEmulatedInterfaceCall(
+                  dexType, invokedMethod, invokeMethod, instructions);
+            }
           }
         }
       }
@@ -448,17 +465,6 @@
     if (dexClass == null) {
       return null;
     }
-    // TODO(b/120884788): Make sure program class are looked up before library class.
-    // Since program classes are desugared, no need to rewrite invokes which can target only
-    // program types.
-    if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
-      return null;
-    }
-    // Since desugared library classes are desugared, no need to rewrite invokes which can target
-    // only such classes program types.
-    if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
-      return null;
-    }
     DexEncodedMethod singleTarget = null;
     if (dexClass.isInterface()) {
       // Look for exact method on the interface.
@@ -934,11 +940,11 @@
     if (appView.options().isDesugaredLibraryCompilation()) {
       generateEmulateInterfaceLibrary(builder);
     }
-    new DesugaredLibraryEmulatedInterfaceDuplicator(appView).duplicateEmulatedInterfaces();
 
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
     processClasses(builder, flavour, synthesizedMethods::add);
+    transformEmulatedInterfaces();
 
     // Process interfaces, create companion or dispatch class if needed, move static
     // methods to companion class, copy default interface methods to companion classes,
@@ -976,6 +982,40 @@
     clear();
   }
 
+  private void transformEmulatedInterfaces() {
+    for (DexType dexType : emulatedInterfaces.keySet()) {
+      DexClass dexClass = appView.definitionFor(dexType);
+      if (dexClass != null && dexClass.isProgramClass()) {
+        transformEmulatedInterfaces(dexClass.asProgramClass());
+      }
+    }
+  }
+
+  // The method transforms emulated interface such as they implement the rewritten version
+  // of each emulated interface they implement. Such change should have no effect on the look-up
+  // results, since each class implementing an emulated interface should also implement the
+  // rewritten one.
+  private void transformEmulatedInterfaces(DexProgramClass clazz) {
+    List<GenericSignature.ClassTypeSignature> newInterfaces = new ArrayList<>();
+    GenericSignature.ClassSignature classSignature = clazz.getClassSignature();
+    for (int i = 0; i < clazz.interfaces.size(); i++) {
+      DexType itf = clazz.interfaces.values[i];
+      assert emulatedInterfaces.containsKey(itf);
+      List<GenericSignature.FieldTypeSignature> typeArguments;
+      if (classSignature == null) {
+        typeArguments = Collections.emptyList();
+      } else {
+        GenericSignature.ClassTypeSignature classTypeSignature =
+            classSignature.superInterfaceSignatures().get(i);
+        assert itf == classTypeSignature.type();
+        typeArguments = classTypeSignature.typeArguments();
+      }
+      newInterfaces.add(
+          new GenericSignature.ClassTypeSignature(emulatedInterfaces.get(itf), typeArguments));
+    }
+    clazz.replaceInterfaces(newInterfaces);
+  }
+
   private void clear() {
     this.cache.clear();
     this.synthesizedMethods.clear();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index c9e47c2..9b8f930 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -233,7 +233,7 @@
         new DexEncodedMethod(
             mainMethod,
             MethodAccessFlags.fromSharedAccessFlags(
-                Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
+                Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC, false),
             MethodTypeSignature.noSignature(),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
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 7bc5e6b..7f99055 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
@@ -22,7 +22,6 @@
   private final DexType nestConstructorType;
   private final Map<DexField, DexMethod> getFieldMap;
   private final Map<DexField, DexMethod> putFieldMap;
-  private final AppView<?> appView;
 
   NestedPrivateMethodLens(
       AppView<?> appView,
@@ -43,7 +42,6 @@
     assert methodMap instanceof IdentityHashMap;
     assert getFieldMap instanceof IdentityHashMap;
     assert putFieldMap instanceof IdentityHashMap;
-    this.appView = appView;
     this.nestConstructorType = nestConstructorType;
     this.getFieldMap = getFieldMap;
     this.putFieldMap = putFieldMap;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 70efbeb..4c61a1c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1129,10 +1129,9 @@
     BasicBlock defaultTarget = theSwitch.fallthroughBlock();
     SwitchCaseEliminator eliminator = null;
     BasicBlockBehavioralSubsumption behavioralSubsumption =
-        new BasicBlockBehavioralSubsumption(appView, code.context());
+        new BasicBlockBehavioralSubsumption(appView, code);
 
     // Compute the set of switch cases that can be removed.
-    int alwaysHitCase = -1;
     for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
       BasicBlock targetBlock = theSwitch.targetBlock(i);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 71684b1..26aa681 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -255,12 +255,21 @@
         }
 
         @Override
+        public boolean isDeadIfInValueIsDead() {
+          return true;
+        }
+
+        @Override
         public Iterable<Value> getValuesRequiredToBeDead() {
           return () -> Iterators.singletonIterator(inValueRequiredToBeDead);
         }
       };
     }
 
+    public boolean isDeadIfInValueIsDead() {
+      return false;
+    }
+
     public boolean isDeadIfOutValueIsDead() {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 89681f0..4c7570c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -116,9 +117,12 @@
           }
         }
 
-        if (appView.options().testing.enableInvokeSuperToInvokeVirtualRewriting) {
-          if (current.isInvokeSuper()) {
-            InvokeSuper invoke = current.asInvokeSuper();
+        if (current.isInvokeSuper()) {
+          InvokeSuper invoke = current.asInvokeSuper();
+
+          // Check if the instruction can be rewritten to invoke-super. This allows inlining of the
+          // enclosing method into contexts outside the current class.
+          if (appView.options().testing.enableInvokeSuperToInvokeVirtualRewriting) {
             DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
             if (singleTarget != null) {
               DexClass holder = appView.definitionForHolder(singleTarget, context);
@@ -134,10 +138,23 @@
               if (newSingleTarget == singleTarget) {
                 it.replaceCurrentInstruction(
                     new InvokeVirtual(invokedMethod, invoke.outValue(), invoke.arguments()));
+                continue;
               }
             }
-            continue;
           }
+
+          // Rebind the invoke to the most specific target.
+          DexMethod invokedMethod = invoke.getInvokedMethod();
+          DexClassAndMethod reboundTarget = rebindSuperInvokeToMostSpecific(invokedMethod, context);
+          if (reboundTarget != null && reboundTarget.getReference() != invokedMethod) {
+            it.replaceCurrentInstruction(
+                new InvokeSuper(
+                    reboundTarget.getReference(),
+                    invoke.outValue(),
+                    invoke.arguments(),
+                    reboundTarget.getHolder().isInterface()));
+          }
+          continue;
         }
 
         if (current.isInvokeVirtual()) {
@@ -274,6 +291,33 @@
     assert code.isConsistentSSA();
   }
 
+  /** This rebinds invoke-super instructions to their most specific target. */
+  private DexClassAndMethod rebindSuperInvokeToMostSpecific(
+      DexMethod target, ProgramMethod context) {
+    DexEncodedMethod definition = appView.appInfo().lookupSuperTarget(target, context);
+    if (definition == null) {
+      return null;
+    }
+
+    DexClass holder = appView.definitionFor(definition.getHolderType());
+    if (holder == null) {
+      assert false;
+      return null;
+    }
+
+    if (holder.isInterface() && holder.getType() != context.getHolder().superType) {
+      // Not allowed.
+      return null;
+    }
+
+    DexClassAndMethod method = DexClassAndMethod.create(holder, definition);
+    if (AccessControl.isMemberAccessible(method, holder, context, appView).isPossiblyFalse()) {
+      return null;
+    }
+
+    return method;
+  }
+
   /**
    * This rebinds invoke-virtual instructions to their most specific target.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 273f7b7..9f790ec 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -365,36 +365,36 @@
     }
 
     public static ConstraintWithTarget deriveConstraint(
-        DexProgramClass context, DexType targetHolder, AccessFlags<?> flags, AppView<?> appView) {
+        ProgramMethod context, DexType targetHolder, AccessFlags<?> flags, AppView<?> appView) {
       if (flags.isPublic()) {
         return ALWAYS;
       } else if (flags.isPrivate()) {
-        if (context.isInANest()) {
-          return NestUtils.sameNest(context.getType(), targetHolder, appView)
+        if (context.getHolder().isInANest()) {
+          return NestUtils.sameNest(context.getHolderType(), targetHolder, appView)
               ? new ConstraintWithTarget(Constraint.SAMENEST, targetHolder)
               : NEVER;
         }
-        return targetHolder == context.type
+        return targetHolder == context.getHolderType()
             ? new ConstraintWithTarget(Constraint.SAMECLASS, targetHolder)
             : NEVER;
       } else if (flags.isProtected()) {
-        if (targetHolder.isSamePackage(context.type)) {
+        if (targetHolder.isSamePackage(context.getHolderType())) {
           // Even though protected, this is visible via the same package from the context.
           return new ConstraintWithTarget(Constraint.PACKAGE, targetHolder);
-        } else if (appView.isSubtype(context.type, targetHolder).isTrue()) {
+        } else if (appView.isSubtype(context.getHolderType(), targetHolder).isTrue()) {
           return new ConstraintWithTarget(Constraint.SUBCLASS, targetHolder);
         }
         return NEVER;
       } else {
         /* package-private */
-        return targetHolder.isSamePackage(context.type)
+        return targetHolder.isSamePackage(context.getHolderType())
             ? new ConstraintWithTarget(Constraint.PACKAGE, targetHolder)
             : NEVER;
       }
     }
 
     public static ConstraintWithTarget classIsVisible(
-        DexProgramClass context, DexType clazz, AppView<?> appView) {
+        ProgramMethod context, DexType clazz, AppView<?> appView) {
       if (clazz.isArrayType()) {
         return classIsVisible(context, clazz.toArrayElementType(appView.dexItemFactory()), appView);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 0968624..ff7668a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -44,8 +45,6 @@
   private GraphLens graphLens;
 
   public InliningConstraints(AppView<AppInfoWithLiveness> appView, GraphLens graphLens) {
-    assert graphLens.isContextFreeForMethods();
-    assert appView.graphLens() != graphLens || graphLens.isIdentityLens();
     this.appView = appView;
     this.graphLens = graphLens; // Note: Intentionally *not* appView.graphLens().
   }
@@ -90,16 +89,15 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
-  public ConstraintWithTarget forDexItemBasedConstString(
-      DexReference type, DexProgramClass context) {
+  public ConstraintWithTarget forDexItemBasedConstString(DexReference type, ProgramMethod context) {
     return ConstraintWithTarget.ALWAYS;
   }
 
-  public ConstraintWithTarget forCheckCast(DexType type, DexProgramClass context) {
+  public ConstraintWithTarget forCheckCast(DexType type, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
-  public ConstraintWithTarget forConstClass(DexType type, DexProgramClass context) {
+  public ConstraintWithTarget forConstClass(DexType type, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
@@ -127,23 +125,23 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
-  public ConstraintWithTarget forInitClass(DexType clazz, DexProgramClass context) {
+  public ConstraintWithTarget forInitClass(DexType clazz, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, clazz, appView);
   }
 
-  public ConstraintWithTarget forInstanceGet(DexField field, DexProgramClass context) {
+  public ConstraintWithTarget forInstanceGet(DexField field, ProgramMethod context) {
     return forFieldInstruction(field, context);
   }
 
-  public ConstraintWithTarget forInstanceOf(DexType type, DexProgramClass context) {
+  public ConstraintWithTarget forInstanceOf(DexType type, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
-  public ConstraintWithTarget forInstancePut(DexField field, DexProgramClass context) {
+  public ConstraintWithTarget forInstancePut(DexField field, ProgramMethod context) {
     return forFieldInstruction(field, context);
   }
 
-  public ConstraintWithTarget forInvoke(DexMethod method, Type type, DexProgramClass context) {
+  public ConstraintWithTarget forInvoke(DexMethod method, Type type, ProgramMethod context) {
     switch (type) {
       case DIRECT:
         return forInvokeDirect(method, context);
@@ -169,8 +167,9 @@
     return ConstraintWithTarget.NEVER;
   }
 
-  public ConstraintWithTarget forInvokeDirect(DexMethod method, DexProgramClass context) {
-    DexMethod lookup = graphLens.lookupMethod(method);
+  public ConstraintWithTarget forInvokeDirect(DexMethod method, ProgramMethod context) {
+    DexMethod lookup =
+        graphLens.lookupMethod(method, context.getReference(), Type.DIRECT).getReference();
     if (lookup.holder.isArrayType()) {
       return ConstraintWithTarget.ALWAYS;
     }
@@ -181,25 +180,27 @@
     return forResolvedMember(resolutionResult.getInitialResolutionHolder(), context, target);
   }
 
-  public ConstraintWithTarget forInvokeInterface(DexMethod method, DexProgramClass context) {
-    DexMethod lookup = graphLens.lookupMethod(method);
+  public ConstraintWithTarget forInvokeInterface(DexMethod method, ProgramMethod context) {
+    DexMethod lookup =
+        graphLens.lookupMethod(method, context.getReference(), Type.INTERFACE).getReference();
     return forVirtualInvoke(lookup, context, true);
   }
 
-  public ConstraintWithTarget forInvokeMultiNewArray(DexType type, DexProgramClass context) {
+  public ConstraintWithTarget forInvokeMultiNewArray(DexType type, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
-  public ConstraintWithTarget forInvokeNewArray(DexType type, DexProgramClass context) {
+  public ConstraintWithTarget forInvokeNewArray(DexType type, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
-  public ConstraintWithTarget forInvokePolymorphic(DexMethod method, DexProgramClass context) {
+  public ConstraintWithTarget forInvokePolymorphic(DexMethod method, ProgramMethod context) {
     return ConstraintWithTarget.NEVER;
   }
 
-  public ConstraintWithTarget forInvokeStatic(DexMethod method, DexProgramClass context) {
-    DexMethod lookup = graphLens.lookupMethod(method);
+  public ConstraintWithTarget forInvokeStatic(DexMethod method, ProgramMethod context) {
+    DexMethod lookup =
+        graphLens.lookupMethod(method, context.getReference(), Type.STATIC).getReference();
     if (lookup.holder.isArrayType()) {
       return ConstraintWithTarget.ALWAYS;
     }
@@ -220,19 +221,21 @@
   @SuppressWarnings("ConstantConditions")
   private DexEncodedMethod singleTargetWhileVerticalClassMerging(
       ResolutionResult resolutionResult,
-      DexProgramClass context,
+      ProgramMethod context,
       TriFunction<ResolutionResult, DexProgramClass, AppInfoWithClassHierarchy, DexEncodedMethod>
           lookup) {
     if (!resolutionResult.isSingleResolution()) {
       return null;
     }
-    DexEncodedMethod dexEncodedMethod = lookup.apply(resolutionResult, context, appView.appInfo());
+    DexEncodedMethod dexEncodedMethod =
+        lookup.apply(resolutionResult, context.getHolder(), appView.appInfo());
     if (!isVerticalClassMerging() || dexEncodedMethod != null) {
       return dexEncodedMethod;
     }
     assert isVerticalClassMerging();
-    assert graphLens.lookupType(context.superType) == context.type;
-    DexProgramClass superContext = appView.programDefinitionFor(context.superType, context);
+    assert graphLens.lookupType(context.getHolder().superType) == context.getHolderType();
+    DexProgramClass superContext =
+        appView.programDefinitionFor(context.getHolder().superType, context);
     if (superContext == null) {
       return null;
     }
@@ -245,13 +248,14 @@
     return null;
   }
 
-  public ConstraintWithTarget forInvokeSuper(DexMethod method, DexProgramClass context) {
+  public ConstraintWithTarget forInvokeSuper(DexMethod method, ProgramMethod context) {
     // The semantics of invoke super depend on the context.
-    return new ConstraintWithTarget(Constraint.SAMECLASS, context.type);
+    return new ConstraintWithTarget(Constraint.SAMECLASS, context.getHolderType());
   }
 
-  public ConstraintWithTarget forInvokeVirtual(DexMethod method, DexProgramClass context) {
-    DexMethod lookup = graphLens.lookupMethod(method);
+  public ConstraintWithTarget forInvokeVirtual(DexMethod method, ProgramMethod context) {
+    DexMethod lookup =
+        graphLens.lookupMethod(method, context.getReference(), Type.VIRTUAL).getReference();
     return forVirtualInvoke(lookup, context, false);
   }
 
@@ -275,7 +279,7 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
-  public ConstraintWithTarget forNewArrayEmpty(DexType type, DexProgramClass context) {
+  public ConstraintWithTarget forNewArrayEmpty(DexType type, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
@@ -283,7 +287,7 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
-  public ConstraintWithTarget forNewInstance(DexType type, DexProgramClass context) {
+  public ConstraintWithTarget forNewInstance(DexType type, ProgramMethod context) {
     return ConstraintWithTarget.classIsVisible(context, type, appView);
   }
 
@@ -299,11 +303,11 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
-  public ConstraintWithTarget forStaticGet(DexField field, DexProgramClass context) {
+  public ConstraintWithTarget forStaticGet(DexField field, ProgramMethod context) {
     return forFieldInstruction(field, context);
   }
 
-  public ConstraintWithTarget forStaticPut(DexField field, DexProgramClass context) {
+  public ConstraintWithTarget forStaticPut(DexField field, ProgramMethod context) {
     return forFieldInstruction(field, context);
   }
 
@@ -331,7 +335,7 @@
     return ConstraintWithTarget.NEVER;
   }
 
-  private ConstraintWithTarget forFieldInstruction(DexField field, DexProgramClass context) {
+  private ConstraintWithTarget forFieldInstruction(DexField field, ProgramMethod context) {
     DexField lookup = graphLens.lookupField(field);
     FieldResolutionResult fieldResolutionResult = appView.appInfo().resolveField(lookup);
     return forResolvedMember(
@@ -341,7 +345,7 @@
   }
 
   private ConstraintWithTarget forVirtualInvoke(
-      DexMethod method, DexProgramClass context, boolean isInterface) {
+      DexMethod method, ProgramMethod context, boolean isInterface) {
     if (method.holder.isArrayType()) {
       return ConstraintWithTarget.ALWAYS;
     }
@@ -359,7 +363,7 @@
 
   private ConstraintWithTarget forResolvedMember(
       DexClass initialResolutionHolder,
-      DexProgramClass context,
+      ProgramMethod context,
       DexEncodedMember<?, ?> resolvedMember) {
     if (resolvedMember == null) {
       // This will fail at runtime.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index f9f4d58..4e89840 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -125,7 +125,7 @@
     }
     // Make sure the target (base) type is visible.
     ConstraintWithTarget constraints =
-        ConstraintWithTarget.classIsVisible(context.getHolder(), baseType, appView);
+        ConstraintWithTarget.classIsVisible(context, baseType, appView);
     if (constraints == ConstraintWithTarget.NEVER) {
       return null;
     }
@@ -201,7 +201,7 @@
     }
     // Make sure the (base) type is visible.
     ConstraintWithTarget constraints =
-        ConstraintWithTarget.classIsVisible(context.getHolder(), baseType, appView);
+        ConstraintWithTarget.classIsVisible(context, baseType, appView);
     if (constraints == ConstraintWithTarget.NEVER) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index d815826..c076942 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -69,7 +70,7 @@
 public class EnumUnboxingRewriter {
 
   public static final String ENUM_UNBOXING_UTILITY_METHOD_PREFIX = "$enumboxing$";
-  private static final int REQUIRED_CLASS_FILE_VERSION = 52;
+  private static final CfVersion REQUIRED_CLASS_FILE_VERSION = CfVersion.V1_8;
 
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
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 ee35087..9ccbbbc 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
@@ -17,6 +17,7 @@
 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.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
@@ -339,7 +340,8 @@
       IRConverter converter,
       OptimizationFeedback feedback,
       Builder<?> builder,
-      ExecutorService executorService)
+      ExecutorService executorService,
+      GraphLens appliedGraphLens)
       throws ExecutionException {
     if (lambdas.isEmpty()) {
       appView.setHorizontallyMergedLambdaClasses(HorizontallyMergedLambdaClasses.empty());
@@ -389,7 +391,7 @@
 
     // Rewrite lambda class references into lambda group class
     // references inside methods from the processing queue.
-    rewriteLambdaReferences(converter, executorService);
+    rewriteLambdaReferences(converter, executorService, appliedGraphLens);
     this.mode = null;
 
     appView.setHorizontallyMergedLambdaClasses(
@@ -454,12 +456,13 @@
     }
   }
 
-  private void rewriteLambdaReferences(IRConverter converter, ExecutorService executorService)
+  private void rewriteLambdaReferences(
+      IRConverter converter, ExecutorService executorService, GraphLens appliedGraphLens)
       throws ExecutionException {
     if (methodsToReprocess.isEmpty()) {
       return;
     }
-    SortedProgramMethodSet methods = methodsToReprocess.build(appView);
+    SortedProgramMethodSet methods = methodsToReprocess.build(appView, appliedGraphLens);
     converter.processMethodsConcurrently(methods, executorService);
     assert methods.stream()
         .map(DexClassAndMethod::getDefinition)
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 b914a14..d96b5d4 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -4,11 +4,10 @@
 package com.android.tools.r8.jar;
 
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
-import static org.objectweb.asm.Opcodes.V1_6;
-import static org.objectweb.asm.Opcodes.V1_8;
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.CodeSizeOverflowDiagnostic;
@@ -85,6 +84,8 @@
 
   public final ProguardMapSupplier proguardMapSupplier;
 
+  private static final CfVersion MIN_VERSION_FOR_COMPILER_GENERATED_CODE = CfVersion.V1_6;
+
   public CfApplicationWriter(
       AppView<?> appView,
       Marker marker,
@@ -168,8 +169,8 @@
     }
     String sourceDebug = getSourceDebugExtension(clazz.annotations());
     writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
-    int version = getClassFileVersion(clazz);
-    if (version >= V1_8) {
+    CfVersion version = getClassFileVersion(clazz);
+    if (version.isGreaterThanOrEqual(CfVersion.V1_8)) {
       // JDK8 and after ignore ACC_SUPER so unset it.
       clazz.accessFlags.unsetSuper();
     } else {
@@ -193,7 +194,7 @@
     for (int i = 0; i < clazz.interfaces.values.length; i++) {
       interfaces[i] = namingLens.lookupInternalName(clazz.interfaces.values[i]);
     }
-    writer.visit(version, access, name, signature, superName, interfaces);
+    writer.visit(version.raw(), access, name, signature, superName, interfaces);
     writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
     ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
 
@@ -238,7 +239,7 @@
         options.reporter, handler -> consumer.accept(ByteDataView.of(result), desc, handler));
   }
 
-  private int getClassFileVersion(DexEncodedMethod method) {
+  private CfVersion getClassFileVersion(DexEncodedMethod method) {
     if (!method.hasClassFileVersion()) {
       // In this case bridges have been introduced for the Cf back-end,
       // which do not have class file version.
@@ -247,18 +248,22 @@
           || options.cfToCfDesugar;
       // TODO(b/146424042): We may call static methods on interface classes so we have to go for
       //  Java 8.
-      return options.cfToCfDesugar ? V1_8 : 0;
+      assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(CfVersion.V1_8);
+      return options.cfToCfDesugar ? CfVersion.V1_8 : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
     }
     return method.getClassFileVersion();
   }
 
-  private int getClassFileVersion(DexProgramClass clazz) {
-    int version = clazz.hasClassFileVersion() ? clazz.getInitialClassFileVersion() : V1_6;
+  private CfVersion getClassFileVersion(DexProgramClass clazz) {
+    CfVersion version =
+        clazz.hasClassFileVersion()
+            ? clazz.getInitialClassFileVersion()
+            : MIN_VERSION_FOR_COMPILER_GENERATED_CODE;
     for (DexEncodedMethod method : clazz.directMethods()) {
-      version = Math.max(version, getClassFileVersion(method));
+      version = version.max(getClassFileVersion(method));
     }
     for (DexEncodedMethod method : clazz.virtualMethods()) {
-      version = Math.max(version, getClassFileVersion(method));
+      version = version.max(getClassFileVersion(method));
     }
     return version;
   }
@@ -335,7 +340,7 @@
 
   private void writeMethod(
       ProgramMethod method,
-      int classFileVersion,
+      CfVersion classFileVersion,
       LensCodeRewriterUtils rewriter,
       ClassWriter writer,
       ImmutableMap<DexString, DexValue> defaults) {
@@ -497,7 +502,7 @@
 
   private void writeCode(
       ProgramMethod method,
-      int classFileVersion,
+      CfVersion classFileVersion,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
     CfCode code = method.getDefinition().getCode().asCfCode();
diff --git a/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
new file mode 100644
index 0000000..7e66078
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/FieldRebindingIdentityLens.java
@@ -0,0 +1,128 @@
+// 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.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.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This lens is used to populate the rebound field reference during lookup, such that both the
+ * non-rebound and rebound field references are available to all descendants of this lens.
+ *
+ * <p>TODO(b/157616970): All uses of this should be replaced by {@link MemberRebindingIdentityLens}.
+ */
+public class FieldRebindingIdentityLens extends NonIdentityGraphLens {
+
+  private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
+
+  private FieldRebindingIdentityLens(
+      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap,
+      DexItemFactory dexItemFactory,
+      GraphLens previousLens) {
+    super(dexItemFactory, previousLens);
+    this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public boolean hasCodeRewritings() {
+    return false;
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    assert previous.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 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
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    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 Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
+        new IdentityHashMap<>();
+
+    private Builder() {}
+
+    void recordDefinitionForNonReboundFieldReference(
+        DexField nonReboundFieldReference, DexField reboundFieldReference) {
+      nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
+    }
+
+    FieldRebindingIdentityLens build(DexItemFactory dexItemFactory) {
+      // This intentionally does not return null when the map is empty. In this case there are no
+      // non-rebound field references, but the member rebinding lens is still needed to populate the
+      // rebound reference during field lookup.
+      return new FieldRebindingIdentityLens(
+          nonReboundFieldReferenceToDefinitionMap, dexItemFactory, GraphLens.getIdentityLens());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 5d3f09b..9d4121c 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,28 +3,31 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
-import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldAccessInfo;
 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.graph.UseRegistry;
 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.ThreadUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
@@ -34,14 +37,14 @@
   private final GraphLens lens;
   private final InternalOptions options;
 
-  private final MemberRebindingLens.Builder builder;
+  private final MemberRebindingLens.Builder lensBuilder;
 
   public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
     assert appView.graphLens().isContextFreeForMethods();
     this.appView = appView;
     this.lens = appView.graphLens();
     this.options = appView.options();
-    this.builder = MemberRebindingLens.builder(appView);
+    this.lensBuilder = MemberRebindingLens.builder(appView);
   }
 
   private DexMethod validTargetFor(DexMethod target, DexMethod original) {
@@ -192,7 +195,8 @@
                       method, target, originalClass, targetClass, lookupTarget);
             }
           }
-          builder.map(method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
+          lensBuilder.map(
+              method, lens.lookupMethod(validTargetFor(target.method, method)), invokeType);
         });
   }
 
@@ -248,11 +252,9 @@
       return false;
     }
     ConstraintWithTarget classVisibility =
-        ConstraintWithTarget.deriveConstraint(
-            context.getHolder(), holderType, holder.accessFlags, appView);
+        ConstraintWithTarget.deriveConstraint(context, holderType, holder.accessFlags, appView);
     ConstraintWithTarget methodVisibility =
-        ConstraintWithTarget.deriveConstraint(
-            context.getHolder(), holderType, method.accessFlags, appView);
+        ConstraintWithTarget.deriveConstraint(context, holderType, method.accessFlags, appView);
     // We may need bridge for visibility if the target class is not visible while the target method
     // is visible from the calling context.
     return classVisibility == ConstraintWithTarget.NEVER
@@ -312,59 +314,132 @@
     return null;
   }
 
-  private void computeFieldRebinding() {
+  private void recordNonReboundFieldAccesses(ExecutorService executorService)
+      throws ExecutionException {
+    assert verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(executorService);
     FieldAccessInfoCollection<?> fieldAccessInfoCollection =
         appView.appInfo().getFieldAccessInfoCollection();
-    fieldAccessInfoCollection.forEach(this::computeFieldRebindingForIndirectAccesses);
+    fieldAccessInfoCollection.forEach(lensBuilder::recordNonReboundFieldAccesses);
   }
 
-  private void computeFieldRebindingForIndirectAccesses(FieldAccessInfo fieldAccessInfo) {
-    fieldAccessInfo.forEachIndirectAccessWithContexts(
-        this::computeFieldRebindingForIndirectAccessWithContexts);
-  }
-
-  private void computeFieldRebindingForIndirectAccessWithContexts(
-      DexField field, ProgramMethodSet contexts) {
-    SuccessfulFieldResolutionResult resolutionResult =
-        appView.appInfo().resolveField(field).asSuccessfulResolution();
-    if (resolutionResult == null) {
-      return;
-    }
-
-    DexClassAndField resolvedField = resolutionResult.getResolutionPair();
-    if (resolvedField.getReference() == field) {
-      assert false;
-      return;
-    }
-
-    // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
-    // are not visible from the access context.
-    boolean accessibleInAllContexts = true;
-    for (ProgramMethod context : contexts) {
-      boolean inaccessibleInContext =
-          AccessControl.isMemberAccessible(
-                  resolvedField, resolutionResult.getResolvedHolder(), context, appView)
-              .isPossiblyFalse();
-      if (inaccessibleInContext) {
-        accessibleInAllContexts = false;
-        break;
-      }
-    }
-
-    if (accessibleInAllContexts) {
-      builder.map(
-          field,
-          lens.lookupField(
-              validTargetFor(resolvedField.getReference(), field, DexClass::lookupField)));
-    }
-  }
-
-  public GraphLens run() {
+  public MemberRebindingLens run(ExecutorService executorService) throws ExecutionException {
     AppInfoWithLiveness appInfo = appView.appInfo();
     computeMethodRebinding(appInfo.getMethodAccessInfoCollection());
-    computeFieldRebinding();
-    GraphLens lens = builder.build(this.lens);
+    recordNonReboundFieldAccesses(executorService);
     appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
-    return lens;
+    return lensBuilder.build();
+  }
+
+  private boolean verifyFieldAccessCollectionContainsAllNonReboundFieldReferences(
+      ExecutorService executorService) throws ExecutionException {
+    Set<DexField> nonReboundFieldReferences = computeNonReboundFieldReferences(executorService);
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+        appView.appInfo().getFieldAccessInfoCollection();
+    fieldAccessInfoCollection.forEach(
+        info -> {
+          DexField reboundFieldReference = info.getField();
+          info.forEachIndirectAccess(
+              nonReboundFieldReference -> {
+                assert reboundFieldReference != nonReboundFieldReference;
+                assert reboundFieldReference
+                    == appView
+                        .appInfo()
+                        .resolveField(nonReboundFieldReference)
+                        .getResolvedFieldReference();
+                nonReboundFieldReferences.remove(nonReboundFieldReference);
+              });
+        });
+    assert nonReboundFieldReferences.isEmpty();
+    return true;
+  }
+
+  private Set<DexField> computeNonReboundFieldReferences(ExecutorService executorService)
+      throws ExecutionException {
+    Set<DexField> nonReboundFieldReferences = Sets.newConcurrentHashSet();
+    ThreadUtils.processItems(
+        appView.appInfo()::forEachMethod,
+        method -> {
+          if (method.getDefinition().hasCode()) {
+            method.registerCodeReferences(
+                new UseRegistry(appView.dexItemFactory()) {
+
+                  @Override
+                  public void registerInstanceFieldRead(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  @Override
+                  public void registerInstanceFieldWrite(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  @Override
+                  public void registerStaticFieldRead(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  @Override
+                  public void registerStaticFieldWrite(DexField field) {
+                    registerFieldReference(field);
+                  }
+
+                  private void registerFieldReference(DexField field) {
+                    SuccessfulFieldResolutionResult resolutionResult =
+                        appView.appInfo().resolveField(field).asSuccessfulResolution();
+                    if (resolutionResult != null
+                        && resolutionResult.getResolvedField().toReference() != field) {
+                      nonReboundFieldReferences.add(field);
+                    }
+                  }
+
+                  @Override
+                  public void registerInitClass(DexType type) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeDirect(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeInterface(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeStatic(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeSuper(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInvokeVirtual(DexMethod method) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerNewInstance(DexType type) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerInstanceOf(DexType type) {
+                    // Intentionally empty.
+                  }
+
+                  @Override
+                  public void registerTypeReference(DexType type) {
+                    // Intentionally empty.
+                  }
+                });
+          }
+        },
+        executorService);
+    return nonReboundFieldReferences;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index 944af37..8e3f4c1 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -165,12 +165,14 @@
       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();
+        if (fieldAccessInfo.getReadsWithContexts().isBottom()) {
+          fieldAccessInfo.setReadsWithContexts(new ConcreteAccessContexts());
+        } else {
+          assert fieldAccessInfo.getReadsWithContexts().isConcrete();
+        }
         // For the purpose of member rebinding, we don't care about the access contexts, so we
         // simply use the empty set.
+        ConcreteAccessContexts accessContexts = fieldAccessInfo.getReadsWithContexts().asConcrete();
         accessContexts.getAccessesWithContexts().put(field, ProgramMethodSet.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 a6a6021..9e23dd0 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -4,88 +4,104 @@
 
 package com.android.tools.r8.optimize;
 
+import static com.android.tools.r8.graph.GraphLens.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
 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.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 com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-public class MemberRebindingLens extends NestedGraphLens {
+public class MemberRebindingLens extends NonIdentityGraphLens {
 
-  public static class Builder {
-
-    private final AppView<?> appView;
-
-    private final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
-    private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps = new IdentityHashMap<>();
-
-    protected Builder(AppView<?> appView) {
-      this.appView = appView;
-    }
-
-    public void map(DexField from, DexField to) {
-      if (from == to) {
-        assert !fieldMap.containsKey(from);
-        return;
-      }
-      fieldMap.put(from, to);
-    }
-
-    public void map(DexMethod from, DexMethod to, Invoke.Type type) {
-      if (from == to) {
-        assert !methodMaps.containsKey(type) || methodMaps.get(type).getOrDefault(from, to) == to;
-        return;
-      }
-      Map<DexMethod, DexMethod> methodMap =
-          methodMaps.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
-      assert methodMap.getOrDefault(from, to) == to;
-      methodMap.put(from, to);
-    }
-
-    public GraphLens build(GraphLens previousLens) {
-      if (fieldMap.isEmpty() && methodMaps.isEmpty()) {
-        return previousLens;
-      }
-      return new MemberRebindingLens(appView, methodMaps, fieldMap, previousLens);
-    }
-  }
-
-  private final AppView<?> appView;
+  private final AppView<AppInfoWithLiveness> appView;
   private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps;
+  private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
 
   public MemberRebindingLens(
-      AppView<?> appView,
+      AppView<AppInfoWithLiveness> appView,
       Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps,
-      Map<DexField, DexField> fieldMap,
-      GraphLens previousLens) {
-    super(
-        ImmutableMap.of(),
-        ImmutableMap.of(),
-        fieldMap,
-        null,
-        null,
-        previousLens,
-        appView.dexItemFactory());
+      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap) {
+    super(appView.dexItemFactory(), appView.graphLens());
     this.appView = appView;
     this.methodMaps = methodMaps;
+    this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
   }
 
-  public static Builder builder(AppView<?> appView) {
+  public static Builder builder(AppView<AppInfoWithLiveness> appView) {
     return new Builder(appView);
   }
 
   @Override
-  public boolean isLegitimateToHaveEmptyMappings() {
+  public boolean isMemberRebindingLens() {
     return true;
   }
 
   @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 this != applied
+        ? getPrevious().getRenamedMethodSignature(originalMethod, applied)
+        : originalMethod;
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  @Override
+  protected DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
+  }
+
+  @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) {
     Map<DexMethod, DexMethod> methodMap =
@@ -97,12 +113,68 @@
     return MethodLookupResult.builder(this)
         .setReference(newMethod)
         .setPrototypeChanges(previous.getPrototypeChanges())
-        .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+        .setType(
+            mapVirtualInterfaceInvocationTypes(
+                appView, newMethod, previous.getReference(), previous.getType()))
         .build();
   }
 
   @Override
-  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return method;
+  }
+
+  public FieldRebindingIdentityLens toRewrittenFieldRebindingLens(
+      DexItemFactory dexItemFactory, GraphLens lens) {
+    FieldRebindingIdentityLens.Builder builder = FieldRebindingIdentityLens.builder();
+    nonReboundFieldReferenceToDefinitionMap.forEach(
+        (nonReboundFieldReference, reboundFieldReference) -> {
+          DexField rewrittenReboundFieldReference = lens.lookupField(reboundFieldReference);
+          DexField rewrittenNonReboundFieldReference =
+              rewrittenReboundFieldReference.withHolder(
+                  lens.lookupType(nonReboundFieldReference.getHolderType()), dexItemFactory);
+          builder.recordDefinitionForNonReboundFieldReference(
+              rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
+        });
+    return builder.build(dexItemFactory);
+  }
+
+  public static class Builder {
+
+    private final AppView<AppInfoWithLiveness> appView;
+    private final Map<Invoke.Type, Map<DexMethod, DexMethod>> methodMaps = new IdentityHashMap<>();
+    private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
+        new IdentityHashMap<>();
+
+    private Builder(AppView<AppInfoWithLiveness> appView) {
+      this.appView = appView;
+    }
+
+    public void map(DexMethod from, DexMethod to, Invoke.Type type) {
+      if (from == to) {
+        assert !methodMaps.containsKey(type) || methodMaps.get(type).getOrDefault(from, to) == to;
+        return;
+      }
+      Map<DexMethod, DexMethod> methodMap =
+          methodMaps.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
+      assert methodMap.getOrDefault(from, to) == to;
+      methodMap.put(from, to);
+    }
+
+    void recordNonReboundFieldAccesses(FieldAccessInfo info) {
+      DexField reboundFieldReference = info.getField();
+      info.forEachIndirectAccess(
+          nonReboundFieldReference ->
+              recordNonReboundFieldAccess(nonReboundFieldReference, reboundFieldReference));
+    }
+
+    private void recordNonReboundFieldAccess(
+        DexField nonReboundFieldReference, DexField reboundFieldReference) {
+      nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
+    }
+
+    public MemberRebindingLens build() {
+      return new MemberRebindingLens(appView, methodMaps, nonReboundFieldReferenceToDefinitionMap);
+    }
   }
 }
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 5d2fef0..52e7b3d 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -40,6 +40,11 @@
   }
 
   @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    return previous;
+  }
+
+  @Override
   public MethodLookupResult internalDescribeLookupMethod(
       MethodLookupResult previous, DexMethod context) {
     if (previous.getType() == Type.DIRECT && publicizedMethods.contains(previous.getReference())) {
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 7da1116..aea3e82 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorCommand.java
@@ -149,7 +149,6 @@
                 .addAdaptResourceFilenames(ProguardPathList.builder().addFileName("**").build())
                 .build(),
             getReporter());
-    assert options.threadCount == ThreadUtils.NOT_SPECIFIED;
     options.relocatorCompilation = true;
     options.threadCount = getThreadCount();
     options.programConsumer = consumer;
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 a0f6645..d3ceea5 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -39,6 +39,11 @@
     return getPrevious().getOriginalType(previous);
   }
 
+  @Override
+  public boolean isSimpleRenaming(DexType from, DexType to) {
+    return originalTypes.get(to) == from || super.isSimpleRenaming(from, to);
+  }
+
   public static class Builder {
 
     protected final BiMap<DexType, DexType> originalTypes = HashBiMap.create();
diff --git a/src/main/java/com/android/tools/r8/retrace/Definition.java b/src/main/java/com/android/tools/r8/retrace/Definition.java
new file mode 100644
index 0000000..f955ac0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/Definition.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.retrace;
+
+import com.android.tools.r8.references.ClassReference;
+
+interface Definition {
+
+  String getName();
+
+  ClassReference getHolderClass();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/FieldDefinition.java b/src/main/java/com/android/tools/r8/retrace/FieldDefinition.java
new file mode 100644
index 0000000..3dcf1c9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/FieldDefinition.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.retrace;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Objects;
+
+/** Internal encoding of a field that allows for having either basic info or full info. */
+abstract class FieldDefinition implements Definition {
+
+  static FieldDefinition create(ClassReference obfuscatedReference, String fieldName) {
+    return new BaseFieldDefinition(obfuscatedReference, fieldName);
+  }
+
+  public static FieldDefinition create(FieldReference field) {
+    return new FullFieldDefinition(field);
+  }
+
+  abstract FieldDefinition substituteHolder(ClassReference newHolder);
+
+  static class BaseFieldDefinition extends FieldDefinition {
+    private final ClassReference classReference;
+    private final String name;
+
+    private BaseFieldDefinition(ClassReference classReference, String name) {
+      this.classReference = classReference;
+      this.name = name;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    FieldDefinition substituteHolder(ClassReference newHolder) {
+      return FieldDefinition.create(classReference, name);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      BaseFieldDefinition that = (BaseFieldDefinition) o;
+      return classReference.equals(that.classReference) && name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(classReference, name);
+    }
+  }
+
+  static class FullFieldDefinition extends FieldDefinition {
+
+    private final FieldReference fieldReference;
+
+    private FullFieldDefinition(FieldReference fieldReference) {
+      this.fieldReference = fieldReference;
+    }
+
+    @Override
+    public String getName() {
+      return fieldReference.getFieldName();
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return fieldReference.getHolderClass();
+    }
+
+    @Override
+    FieldDefinition substituteHolder(ClassReference newHolder) {
+      return create(
+          Reference.field(newHolder, fieldReference.getFieldName(), fieldReference.getFieldType()));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      FullFieldDefinition that = (FullFieldDefinition) o;
+      return fieldReference.equals(that.fieldReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return fieldReference.hashCode();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/MethodDefinition.java b/src/main/java/com/android/tools/r8/retrace/MethodDefinition.java
new file mode 100644
index 0000000..8641f13
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MethodDefinition.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.retrace;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Objects;
+
+/** Internal encoding of a method that allows for having either basic info or full info. */
+abstract class MethodDefinition implements Definition {
+
+  static MethodDefinition create(ClassReference classReference, String methodName) {
+    return new BaseMethodDefinition(classReference, methodName);
+  }
+
+  static MethodDefinition create(MethodReference methodReference) {
+    return new FullMethodDefinition(methodReference);
+  }
+
+  boolean isFullMethodDefinition() {
+    return false;
+  }
+
+  FullMethodDefinition asFullMethodDefinition() {
+    return null;
+  }
+
+  abstract MethodDefinition substituteHolder(ClassReference newHolder);
+
+  static class BaseMethodDefinition extends MethodDefinition {
+
+    private final ClassReference classReference;
+    private final String name;
+
+    private BaseMethodDefinition(ClassReference classReference, String name) {
+      this.classReference = classReference;
+      this.name = name;
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return classReference;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    MethodDefinition substituteHolder(ClassReference newHolder) {
+      return MethodDefinition.create(newHolder, name);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      BaseMethodDefinition that = (BaseMethodDefinition) o;
+      return classReference.equals(that.classReference) && name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(classReference, name);
+    }
+  }
+
+  static class FullMethodDefinition extends MethodDefinition {
+
+    private final MethodReference methodReference;
+
+    private FullMethodDefinition(MethodReference methodReference) {
+      this.methodReference = methodReference;
+    }
+
+    @Override
+    public ClassReference getHolderClass() {
+      return methodReference.getHolderClass();
+    }
+
+    @Override
+    public String getName() {
+      return methodReference.getMethodName();
+    }
+
+    @Override
+    boolean isFullMethodDefinition() {
+      return true;
+    }
+
+    @Override
+    FullMethodDefinition asFullMethodDefinition() {
+      return this;
+    }
+
+    @Override
+    MethodDefinition substituteHolder(ClassReference newHolder) {
+      return MethodDefinition.create(
+          Reference.method(
+              newHolder,
+              methodReference.getMethodName(),
+              methodReference.getFormalTypes(),
+              methodReference.getReturnType()));
+    }
+
+    MethodReference getMethodReference() {
+      return methodReference;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      FullMethodDefinition that = (FullMethodDefinition) o;
+      return methodReference.equals(that.methodReference);
+    }
+
+    @Override
+    public int hashCode() {
+      return methodReference.hashCode();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/PlainStackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/PlainStackTraceVisitor.java
new file mode 100644
index 0000000..1234b4d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PlainStackTraceVisitor.java
@@ -0,0 +1,229 @@
+// Copyright (c) 2019, 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;
+
+import static com.google.common.base.Predicates.not;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.retrace.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public final class PlainStackTraceVisitor
+    implements StackTraceVisitor<StackTraceElementStringProxy> {
+
+  private final List<String> stackTrace;
+  private final DiagnosticsHandler diagnosticsHandler;
+
+  PlainStackTraceVisitor(List<String> stackTrace, DiagnosticsHandler diagnosticsHandler) {
+    this.stackTrace = stackTrace;
+    this.diagnosticsHandler = diagnosticsHandler;
+  }
+
+  @Override
+  public void forEach(Consumer<StackTraceElementStringProxy> consumer) {
+    for (int i = 0; i < stackTrace.size(); i++) {
+      consumer.accept(parseLine(i + 1, stackTrace.get(i)));
+    }
+  }
+
+  static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
+    return firstFromIndex(line, index, not(Character::isWhitespace));
+  }
+
+  static int firstCharFromIndex(String line, int index, char ch) {
+    return firstFromIndex(line, index, c -> c == ch);
+  }
+
+  static int firstFromIndex(String line, int index, Predicate<Character> predicate) {
+    for (int i = index; i < line.length(); i++) {
+      if (predicate.test(line.charAt(i))) {
+        return i;
+      }
+    }
+    return line.length();
+  }
+
+  /**
+   * Captures a stack trace line of the following formats:
+   *
+   * <ul>
+   *   <li>com.android.r8.R8Exception
+   *   <li>com.android.r8.R8Exception: Problem when compiling program
+   *   <li>Caused by: com.android.r8.R8InnerException: You have to write the program first
+   *   <li>com.android.r8.R8InnerException: You have to write the program first
+   * </ul>
+   *
+   * <p>This will also contains false positives, such as
+   *
+   * <pre>
+   *   W( 8207) VFY: unable to resolve static method 11: Lprivateinterfacemethods/I$-CC;....
+   * </pre>
+   *
+   * <p>The only invalid chars for type-identifiers for a java type-name is ';', '[' and '/', so we
+   * cannot really disregard the above line.
+   *
+   * <p>Caused by and Suppressed seems to not change based on locale, so we use these as markers.
+   */
+  private static class ExceptionLine {
+
+    private static final String CAUSED_BY = "Caused by: ";
+    private static final String SUPPRESSED = "Suppressed: ";
+
+    private static StackTraceElementStringProxy tryParse(String line) {
+      if (line.isEmpty()) {
+        return null;
+      }
+      int firstNonWhiteSpaceChar = firstNonWhiteSpaceCharacterFromIndex(line, 0);
+      String description = "";
+      if (line.startsWith(CAUSED_BY, firstNonWhiteSpaceChar)) {
+        description = CAUSED_BY;
+      } else if (line.startsWith(SUPPRESSED, firstNonWhiteSpaceChar)) {
+        description = SUPPRESSED;
+      }
+      int exceptionStartIndex = firstNonWhiteSpaceChar + description.length();
+      int messageStartIndex = firstCharFromIndex(line, exceptionStartIndex, ':');
+      String className = line.substring(exceptionStartIndex, messageStartIndex);
+      if (!DescriptorUtils.isValidJavaType(className)) {
+        return null;
+      }
+      return StackTraceElementStringProxy.builder(line)
+          .registerClassName(exceptionStartIndex, messageStartIndex)
+          .build();
+    }
+  }
+
+  /**
+   * Captures a stack trace line on the following form
+   *
+   * <ul>
+   *   <li>at dalvik.system.NativeStart.main(NativeStart.java:99)
+   *   <li>at dalvik.system.NativeStart.main(:99)
+   *   <li>at dalvik.system.NativeStart.main(Foo.java:)
+   *   <li>at dalvik.system.NativeStart.main(Native Method)
+   *   <li>at classloader/named_module@version/foo.bar.baz(:20)
+   *   <li>at classloader//foo.bar.baz(:20)
+   * </ul>
+   *
+   * <p>Empirical evidence suggests that the "at" string is never localized.
+   */
+  private static class AtLine {
+
+    private static StackTraceElementStringProxy tryParse(String line) {
+      // Check that the line is indented with some amount of white space.
+      if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
+        return null;
+      }
+      // Find the first non-white space character and check that we have the sequence 'a', 't', ' '.
+      int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
+      if (firstNonWhiteSpace + 2 >= line.length()
+          || line.charAt(firstNonWhiteSpace) != 'a'
+          || line.charAt(firstNonWhiteSpace + 1) != 't'
+          || line.charAt(firstNonWhiteSpace + 2) != ' ') {
+        return null;
+      }
+      int classClassLoaderOrModuleStartIndex =
+          firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
+      if (classClassLoaderOrModuleStartIndex >= line.length()
+          || classClassLoaderOrModuleStartIndex != firstNonWhiteSpace + 3) {
+        return null;
+      }
+      int parensStart = firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '(');
+      if (parensStart >= line.length()) {
+        return null;
+      }
+      int parensEnd = firstCharFromIndex(line, parensStart, ')');
+      if (parensEnd >= line.length()) {
+        return null;
+      }
+      if (firstNonWhiteSpaceCharacterFromIndex(line, parensEnd) == line.length()) {
+        return null;
+      }
+      int methodSeparator = line.lastIndexOf('.', parensStart);
+      if (methodSeparator <= classClassLoaderOrModuleStartIndex) {
+        return null;
+      }
+      int classStartIndex = classClassLoaderOrModuleStartIndex;
+      int classLoaderOrModuleEndIndex =
+          firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '/');
+      if (classLoaderOrModuleEndIndex < methodSeparator) {
+        int moduleEndIndex = firstCharFromIndex(line, classLoaderOrModuleEndIndex + 1, '/');
+        if (moduleEndIndex < methodSeparator) {
+          // The stack trace contains both a class loader and module
+          classStartIndex = moduleEndIndex + 1;
+        } else {
+          classStartIndex = classLoaderOrModuleEndIndex + 1;
+        }
+      }
+      StackTraceElementStringProxyBuilder builder =
+          StackTraceElementStringProxy.builder(line)
+              .registerClassName(classStartIndex, methodSeparator)
+              .registerMethodName(methodSeparator + 1, parensStart);
+      // Check if we have a filename and position.
+      int separatorIndex = firstCharFromIndex(line, parensStart, ':');
+      if (separatorIndex < parensEnd) {
+        builder.registerSourceFile(parensStart + 1, separatorIndex);
+        builder.registerLineNumber(separatorIndex + 1, parensEnd);
+      } else {
+        builder.registerSourceFile(parensStart + 1, parensEnd);
+      }
+      return builder.build();
+    }
+  }
+
+  static class CircularReferenceLine {
+
+    private static final String CIRCULAR_REFERENCE = "[CIRCULAR REFERENCE:";
+
+    static StackTraceElementStringProxy tryParse(String line) {
+      // Check that the line is indented with some amount of white space.
+      if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
+        return null;
+      }
+      // Find the first non-white space character and check that we have the sequence
+      // '[CIRCULAR REFERENCE:Exception]'.
+      int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
+      if (!line.startsWith(CIRCULAR_REFERENCE, firstNonWhiteSpace)) {
+        return null;
+      }
+      int exceptionStartIndex = firstNonWhiteSpace + CIRCULAR_REFERENCE.length();
+      int lastBracketPosition = firstCharFromIndex(line, exceptionStartIndex, ']');
+      if (lastBracketPosition == line.length()) {
+        return null;
+      }
+      int onlyWhitespaceFromLastBracket =
+          firstNonWhiteSpaceCharacterFromIndex(line, lastBracketPosition + 1);
+      if (onlyWhitespaceFromLastBracket != line.length()) {
+        return null;
+      }
+      return StackTraceElementStringProxy.builder(line)
+          .registerClassName(exceptionStartIndex, lastBracketPosition)
+          .build();
+    }
+  }
+
+  private StackTraceElementStringProxy parseLine(int lineNumber, String line) {
+    if (line == null) {
+      diagnosticsHandler.error(RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
+      throw new Retrace.RetraceAbortException();
+    }
+    // Most lines are 'at lines' so attempt to parse it first.
+    StackTraceElementStringProxy parsedLine = AtLine.tryParse(line);
+    if (parsedLine != null) {
+      return parsedLine;
+    }
+    parsedLine = ExceptionLine.tryParse(line);
+    if (parsedLine != null) {
+      return parsedLine;
+    }
+    parsedLine = CircularReferenceLine.tryParse(line);
+    if (parsedLine != null) {
+      return parsedLine;
+    }
+    return StackTraceElementStringProxy.builder(line).build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Result.java b/src/main/java/com/android/tools/r8/retrace/Result.java
index 12e92d3..692ca9b 100644
--- a/src/main/java/com/android/tools/r8/retrace/Result.java
+++ b/src/main/java/com/android/tools/r8/retrace/Result.java
@@ -14,4 +14,6 @@
   public abstract Stream<R> stream();
 
   public abstract RR forEach(Consumer<R> resultConsumer);
+
+  public abstract boolean isAmbiguous();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index adf9583..b8d94e6 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -49,7 +49,8 @@
 
   public static final String USAGE_MESSAGE =
       StringUtils.lines(
-          "Usage: retrace <proguard-map> [stack-trace-file] [--regex <regexp>, --verbose, --info]",
+          "Usage: retrace <proguard-map> [stack-trace-file] "
+              + "[--regex <regexp>, --verbose, --info, --quiet]",
           "  where <proguard-map> is an r8 generated mapping file.");
 
   private static Builder parseArguments(String[] args, DiagnosticsHandler diagnosticsHandler) {
@@ -58,6 +59,7 @@
     boolean hasSetProguardMap = false;
     boolean hasSetStackTrace = false;
     boolean hasSetRegularExpression = false;
+    boolean hasSetQuiet = false;
     while (context.head() != null) {
       Boolean help = OptionsParsing.tryParseBoolean(context, "--help");
       if (help != null) {
@@ -77,6 +79,11 @@
         builder.setVerbose(true);
         continue;
       }
+      Boolean quiet = OptionsParsing.tryParseBoolean(context, "--quiet");
+      if (quiet != null) {
+        hasSetQuiet = true;
+        continue;
+      }
       String regex = OptionsParsing.tryParseSingle(context, "--regex", "r");
       if (regex != null && !regex.isEmpty()) {
         builder.setRegularExpression(regex);
@@ -104,7 +111,7 @@
       throw new RetraceAbortException();
     }
     if (!hasSetStackTrace) {
-      builder.setStackTrace(getStackTraceFromStandardInput());
+      builder.setStackTrace(getStackTraceFromStandardInput(hasSetQuiet));
     }
     if (!hasSetRegularExpression) {
       builder.setRegularExpression(DEFAULT_REGULAR_EXPRESSION);
@@ -165,10 +172,27 @@
                     command.isVerbose)
                 .retrace();
       } else {
-        result =
-            new RetraceStackTrace(
-                    retracer, command.stackTrace, command.diagnosticsHandler, command.isVerbose)
-                .retrace();
+        PlainStackTraceVisitor plainStackTraceVisitor =
+            new PlainStackTraceVisitor(command.stackTrace, command.diagnosticsHandler);
+        StackTraceElementProxyRetracer<StackTraceElementStringProxy> proxyRetracer =
+            new StackTraceElementProxyRetracer<>(retracer);
+        List<String> retracedStrings = new ArrayList<>();
+        plainStackTraceVisitor.forEach(
+            stackTraceElement -> {
+              List<String> retracedStringsForElement = new ArrayList<>();
+              proxyRetracer
+                  .retrace(stackTraceElement)
+                  .forEach(
+                      retracedElement -> {
+                        StackTraceElementStringProxy originalItem =
+                            retracedElement.getOriginalItem();
+                        retracedStringsForElement.add(
+                            originalItem.toRetracedItem(
+                                retracedElement, !retracedStringsForElement.isEmpty()));
+                      });
+              retracedStrings.addAll(retracedStringsForElement);
+            });
+        result = new RetraceCommandLineResult(retracedStrings);
       }
       timing.end();
       timing.begin("Report result");
@@ -238,8 +262,10 @@
     withMainProgramHandler(() -> run(args));
   }
 
-  private static List<String> getStackTraceFromStandardInput() {
-    System.out.println("Waiting for stack-trace input...");
+  private static List<String> getStackTraceFromStandardInput(boolean printWaitingMessage) {
+    if (!printWaitingMessage) {
+      System.out.println("Waiting for stack-trace input...");
+    }
     Scanner sc = new Scanner(new InputStreamReader(System.in, Charsets.UTF_8));
     List<String> readLines = new ArrayList<>();
     while (sc.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceApi.java b/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
index 7bb9232..7a1eb4e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceApi.java
@@ -14,11 +14,15 @@
 @Keep
 public interface RetraceApi {
 
+  // TODO(b/170711681): Rename these to not have overloads.
+
+  RetraceClassResult retrace(ClassReference classReference);
+
   RetraceMethodResult retrace(MethodReference methodReference);
 
   RetraceFieldResult retrace(FieldReference fieldReference);
 
-  RetraceClassResult retrace(ClassReference classReference);
+  RetraceFrameResult retrace(MethodReference methodReference, int position);
 
   RetraceTypeResult retrace(TypeReference typeReference);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java
new file mode 100644
index 0000000..c21a100
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassMemberElement.java
@@ -0,0 +1,28 @@
+// 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;
+
+import java.util.function.BiConsumer;
+
+public interface RetraceClassMemberElement<T extends RetracedClassMember> {
+
+  boolean isUnknown();
+
+  default boolean isFrameElement() {
+    return false;
+  }
+
+  default RetraceFrameResult.Element asFrameElement() {
+    return null;
+  }
+
+  RetraceClassResult.Element getClassElement();
+
+  T getMember();
+
+  void visitFrames(BiConsumer<T, Integer> consumer);
+
+  RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile);
+}
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 945aa0f..795311e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -14,8 +14,11 @@
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
-import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -41,8 +44,28 @@
   }
 
   public RetraceFieldResult lookupField(String fieldName) {
+    return lookupField(FieldDefinition.create(obfuscatedReference, fieldName));
+  }
+
+  public RetraceFieldResult lookupField(String fieldName, TypeReference fieldType) {
+    return lookupField(
+        FieldDefinition.create(Reference.field(obfuscatedReference, fieldName, fieldType)));
+  }
+
+  public RetraceMethodResult lookupMethod(String methodName) {
+    return lookupMethod(MethodDefinition.create(obfuscatedReference, methodName));
+  }
+
+  public RetraceMethodResult lookupMethod(
+      String methodName, List<TypeReference> formalTypes, TypeReference returnType) {
+    return lookupMethod(
+        MethodDefinition.create(
+            Reference.method(obfuscatedReference, methodName, formalTypes, returnType)));
+  }
+
+  private RetraceFieldResult lookupField(FieldDefinition fieldDefinition) {
     return lookup(
-        fieldName,
+        fieldDefinition,
         (mapper, name) -> {
           List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
           if (memberNamings == null || memberNamings.isEmpty()) {
@@ -53,34 +76,37 @@
         RetraceFieldResult::new);
   }
 
-  public RetraceMethodResult lookupMethod(String methodName) {
+  private RetraceMethodResult lookupMethod(MethodDefinition methodDefinition) {
     return lookup(
-        methodName,
+        methodDefinition,
         (mapper, name) -> {
           MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
           if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
             return null;
           }
-          return mappedRanges;
+          return mappedRanges.getMappedRanges();
         },
         RetraceMethodResult::new);
   }
 
-  private <T, R> R lookup(
-      String name,
+  private <T, R, D extends Definition> R lookup(
+      D definition,
       BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
-      ResultConstructor<T, R> constructor) {
-    Box<R> elementBox = new Box<>();
+      ResultConstructor<T, R, D> constructor) {
+    List<Pair<Element, T>> mappings = new ArrayList<>();
     forEach(
         element -> {
-          assert !elementBox.isSet();
-          T mappedRangesForT = null;
-          if (element.mapper != null) {
-            mappedRangesForT = lookupFunction.apply(element.mapper, name);
+          if (mapper != null) {
+            assert element.mapper != null;
+            T mappedElements = lookupFunction.apply(element.mapper, definition.getName());
+            if (mappedElements != null) {
+              mappings.add(new Pair<>(element, mappedElements));
+              return;
+            }
           }
-          elementBox.set(constructor.create(element, mappedRangesForT, name, retracer));
+          mappings.add(new Pair<>(element, null));
         });
-    return elementBox.get();
+    return constructor.create(this, mappings, definition, retracer);
   }
 
   boolean hasRetraceResult() {
@@ -105,10 +131,15 @@
     return this;
   }
 
-  private interface ResultConstructor<T, R> {
-    R create(Element element, T mappings, String obfuscatedName, RetraceApi retraceApi);
+  private interface ResultConstructor<T, R, D> {
+    R create(
+        RetraceClassResult classResult,
+        List<Pair<Element, T>> mappings,
+        D definition,
+        RetraceApi retraceApi);
   }
 
+  @Override
   public boolean isAmbiguous() {
     // Currently we have no way of producing ambiguous class results.
     return false;
@@ -160,8 +191,12 @@
     }
 
     public RetraceFieldResult lookupField(String fieldName) {
+      return lookupField(FieldDefinition.create(classReference.getClassReference(), fieldName));
+    }
+
+    private RetraceFieldResult lookupField(FieldDefinition fieldDefinition) {
       return lookup(
-          fieldName,
+          fieldDefinition,
           (mapper, name) -> {
             List<MemberNaming> memberNamings = mapper.mappedFieldNamingsByName.get(name);
             if (memberNamings == null || memberNamings.isEmpty()) {
@@ -173,27 +208,37 @@
     }
 
     public RetraceMethodResult lookupMethod(String methodName) {
+      return lookupMethod(MethodDefinition.create(classReference.getClassReference(), methodName));
+    }
+
+    private RetraceMethodResult lookupMethod(MethodDefinition methodDefinition) {
       return lookup(
-          methodName,
+          methodDefinition,
           (mapper, name) -> {
             MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(name);
             if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
               return null;
             }
-            return mappedRanges;
+            return mappedRanges.getMappedRanges();
           },
           RetraceMethodResult::new);
     }
 
-    private <T, R> R lookup(
-        String name,
+    private <T, R, D extends Definition> R lookup(
+        D definition,
         BiFunction<ClassNamingForNameMapper, String, T> lookupFunction,
-        ResultConstructor<T, R> constructor) {
-      return constructor.create(
-          this,
-          mapper != null ? lookupFunction.apply(mapper, name) : null,
-          name,
-          classResult.retracer);
+        ResultConstructor<T, R, D> constructor) {
+      List<Pair<Element, T>> mappings = ImmutableList.of();
+      if (mapper != null) {
+        T result = lookupFunction.apply(mapper, definition.getName());
+        if (result != null) {
+          mappings = ImmutableList.of(new Pair<>(this, result));
+        }
+      }
+      if (mappings.isEmpty()) {
+        mappings = ImmutableList.of(new Pair<>(this, null));
+      }
+      return constructor.create(classResult, mappings, definition, 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 265b3b2..46f247d 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFieldResult.java
@@ -9,79 +9,83 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
 import java.util.List;
-import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 @Keep
 public class RetraceFieldResult extends Result<RetraceFieldResult.Element, RetraceFieldResult> {
 
-  private final RetraceClassResult.Element classElement;
-  private final List<MemberNaming> memberNamings;
-  private final String obfuscatedName;
+  private final RetraceClassResult classResult;
+  private final List<Pair<RetraceClassResult.Element, List<MemberNaming>>> memberNamings;
+  private final FieldDefinition fieldDefinition;
   private final RetraceApi retracer;
 
   RetraceFieldResult(
-      RetraceClassResult.Element classElement,
-      List<MemberNaming> memberNamings,
-      String obfuscatedName,
+      RetraceClassResult classResult,
+      List<Pair<RetraceClassResult.Element, List<MemberNaming>>> memberNamings,
+      FieldDefinition fieldDefinition,
       RetraceApi retracer) {
-    this.classElement = classElement;
+    this.classResult = classResult;
     this.memberNamings = memberNamings;
-    this.obfuscatedName = obfuscatedName;
+    this.fieldDefinition = fieldDefinition;
     this.retracer = retracer;
-    assert classElement != null;
-    assert memberNamings == null
-        || (!memberNamings.isEmpty() && memberNamings.stream().allMatch(Objects::nonNull));
+    assert classResult != null;
+    assert !memberNamings.isEmpty();
   }
 
-  private boolean hasRetraceResult() {
-    return memberNamings != null;
-  }
-
+  @Override
   public boolean isAmbiguous() {
-    if (!hasRetraceResult()) {
+    if (memberNamings.size() > 1) {
+      return true;
+    }
+    List<MemberNaming> mappings = memberNamings.get(0).getSecond();
+    if (mappings == null) {
       return false;
     }
-    assert memberNamings != null;
-    return memberNamings.size() > 1;
-  }
-
-  public RetracedField getUnknownReference() {
-    return RetracedField.createUnknown(classElement.getRetracedClass(), obfuscatedName);
+    return mappings.size() > 1;
   }
 
   @Override
   public Stream<Element> stream() {
-    if (!hasRetraceResult()) {
-      return Stream.of(new Element(this, classElement, getUnknownReference()));
-    }
-    assert !memberNamings.isEmpty();
     return memberNamings.stream()
-        .map(
-            memberNaming -> {
-              assert memberNaming.isFieldNaming();
-              FieldSignature fieldSignature =
-                  memberNaming.getOriginalSignature().asFieldSignature();
-              RetracedClass holder =
-                  fieldSignature.isQualified()
-                      ? RetracedClass.create(
-                          Reference.classFromDescriptor(
-                              DescriptorUtils.javaTypeToDescriptor(
-                                  fieldSignature.toHolderFromQualified())))
-                      : classElement.getRetracedClass();
-              return new Element(
-                  this,
-                  classElement,
-                  RetracedField.create(
-                      holder,
-                      Reference.field(
-                          holder.getClassReference(),
-                          fieldSignature.isQualified()
-                              ? fieldSignature.toUnqualifiedName()
-                              : fieldSignature.name,
-                          Reference.typeFromTypeName(fieldSignature.type))));
+        .flatMap(
+            mappedNamePair -> {
+              RetraceClassResult.Element classElement = mappedNamePair.getFirst();
+              List<MemberNaming> memberNamings = mappedNamePair.getSecond();
+              if (memberNamings == null) {
+                return Stream.of(
+                    new RetraceFieldResult.Element(
+                        this,
+                        classElement,
+                        RetracedField.create(
+                            fieldDefinition.substituteHolder(
+                                classElement.getRetracedClass().getClassReference()))));
+              }
+              return memberNamings.stream()
+                  .map(
+                      memberNaming -> {
+                        FieldSignature fieldSignature =
+                            memberNaming.getOriginalSignature().asFieldSignature();
+                        RetracedClass holder =
+                            fieldSignature.isQualified()
+                                ? RetracedClass.create(
+                                    Reference.classFromDescriptor(
+                                        DescriptorUtils.javaTypeToDescriptor(
+                                            fieldSignature.toHolderFromQualified())))
+                                : classElement.getRetracedClass();
+                        return new Element(
+                            this,
+                            classElement,
+                            RetracedField.create(
+                                Reference.field(
+                                    holder.getClassReference(),
+                                    fieldSignature.isQualified()
+                                        ? fieldSignature.toUnqualifiedName()
+                                        : fieldSignature.name,
+                                    Reference.typeFromTypeName(fieldSignature.type))));
+                      });
             });
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java
new file mode 100644
index 0000000..95dc9d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFrameResult.java
@@ -0,0 +1,164 @@
+// 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;
+
+import static com.android.tools.r8.retrace.RetraceUtils.methodReferenceFromMappedRange;
+
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+public class RetraceFrameResult extends Result<RetraceFrameResult.Element, RetraceFrameResult> {
+
+  private final RetraceClassResult classResult;
+  private final MethodDefinition methodDefinition;
+  private final int obfuscatedPosition;
+  private final List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges;
+  private final RetraceApi retracer;
+
+  public RetraceFrameResult(
+      RetraceClassResult classResult,
+      List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges,
+      MethodDefinition methodDefinition,
+      int obfuscatedPosition,
+      RetraceApi retracer) {
+    this.classResult = classResult;
+    this.methodDefinition = methodDefinition;
+    this.obfuscatedPosition = obfuscatedPosition;
+    this.mappedRanges = mappedRanges;
+    this.retracer = retracer;
+  }
+
+  @Override
+  public boolean isAmbiguous() {
+    return mappedRanges.size() > 1;
+  }
+
+  @Override
+  public Stream<Element> stream() {
+    return mappedRanges.stream()
+        .map(
+            mappedRangePair -> {
+              RetraceClassResult.Element classElement = mappedRangePair.getFirst();
+              List<MappedRange> mappedRanges = mappedRangePair.getSecond();
+              if (mappedRanges == null || mappedRanges.isEmpty()) {
+                return new Element(
+                    this,
+                    classElement,
+                    RetracedMethod.create(
+                        methodDefinition.substituteHolder(
+                            classElement.getRetracedClass().getClassReference())),
+                    ImmutableList.of(),
+                    obfuscatedPosition);
+              }
+              MappedRange mappedRange = mappedRanges.get(0);
+              MethodReference methodReference =
+                  methodReferenceFromMappedRange(
+                      mappedRange, classElement.getRetracedClass().getClassReference());
+              RetracedMethod retracedMethod =
+                  RetracedMethod.create(
+                      methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition));
+              return new Element(
+                  this, classElement, retracedMethod, mappedRanges, obfuscatedPosition);
+            });
+  }
+
+  @Override
+  public RetraceFrameResult forEach(Consumer<Element> resultConsumer) {
+    stream().forEach(resultConsumer);
+    return this;
+  }
+
+  public static class Element implements RetraceClassMemberElement<RetracedMethod> {
+
+    private final RetracedMethod methodReference;
+    private final RetraceFrameResult retraceFrameResult;
+    private final RetraceClassResult.Element classElement;
+    private final List<MappedRange> mappedRanges;
+    private final int obfuscatedPosition;
+
+    public Element(
+        RetraceFrameResult retraceFrameResult,
+        RetraceClassResult.Element classElement,
+        RetracedMethod methodReference,
+        List<MappedRange> mappedRanges,
+        int obfuscatedPosition) {
+      this.methodReference = methodReference;
+      this.retraceFrameResult = retraceFrameResult;
+      this.classElement = classElement;
+      this.mappedRanges = mappedRanges;
+      this.obfuscatedPosition = obfuscatedPosition;
+    }
+
+    @Override
+    public boolean isUnknown() {
+      return methodReference.isUnknown();
+    }
+
+    @Override
+    public boolean isFrameElement() {
+      return true;
+    }
+
+    @Override
+    public Element asFrameElement() {
+      return this;
+    }
+
+    @Override
+    public RetracedMethod getMember() {
+      return methodReference;
+    }
+
+    public RetracedMethod getTopFrame() {
+      return methodReference;
+    }
+
+    @Override
+    public RetraceClassResult.Element getClassElement() {
+      return classElement;
+    }
+
+    @Override
+    public void visitFrames(BiConsumer<RetracedMethod, Integer> consumer) {
+      int counter = 0;
+      consumer.accept(methodReference, counter++);
+      for (RetracedMethod outerFrame : getOuterFrames()) {
+        consumer.accept(outerFrame, counter++);
+      }
+    }
+
+    @Override
+    public RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String sourceFile) {
+      return RetraceUtils.getSourceFile(
+          classElement, frame.getHolderClass(), sourceFile, retraceFrameResult.retracer);
+    }
+
+    public List<RetracedMethod> getOuterFrames() {
+      if (mappedRanges == null) {
+        return Collections.emptyList();
+      }
+      List<RetracedMethod> outerFrames = new ArrayList<>();
+      for (int i = 1; i < mappedRanges.size(); i++) {
+        MappedRange mappedRange = mappedRanges.get(i);
+        MethodReference methodReference =
+            methodReferenceFromMappedRange(
+                mappedRange, classElement.getRetracedClass().getClassReference());
+        outerFrames.add(
+            RetracedMethod.create(
+                MethodDefinition.create(methodReference),
+                mappedRange.getOriginalLineNumber(obfuscatedPosition)));
+      }
+      return outerFrames;
+    }
+  }
+}
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 93c87e8..0906420 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -7,121 +7,116 @@
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.naming.Range;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 @Keep
 public class RetraceMethodResult extends Result<RetraceMethodResult.Element, RetraceMethodResult> {
 
-  private final String obfuscatedName;
-  private final RetraceClassResult.Element classElement;
-  private final MappedRangesOfName mappedRanges;
+  private final MethodDefinition methodDefinition;
+  private final RetraceClassResult classResult;
+  private final List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges;
   private final RetraceApi retracer;
-  private Boolean isAmbiguousCached = null;
 
   RetraceMethodResult(
-      RetraceClassResult.Element classElement,
-      MappedRangesOfName mappedRanges,
-      String obfuscatedName,
+      RetraceClassResult classResult,
+      List<Pair<RetraceClassResult.Element, List<MappedRange>>> mappedRanges,
+      MethodDefinition methodDefinition,
       RetraceApi retracer) {
-    this.classElement = classElement;
+    this.classResult = classResult;
     this.mappedRanges = mappedRanges;
-    this.obfuscatedName = obfuscatedName;
+    this.methodDefinition = methodDefinition;
     this.retracer = retracer;
-    assert classElement != null;
+    assert classResult != null;
+    assert !mappedRanges.isEmpty();
   }
 
-  public RetracedMethod getUnknownReference() {
-    return RetracedMethod.createUnknown(classElement.getRetracedClass(), obfuscatedName);
-  }
-
-  private boolean hasRetraceResult() {
-    return mappedRanges != null && mappedRanges.getMappedRanges().size() > 0;
-  }
-
+  @Override
   public boolean isAmbiguous() {
-    if (isAmbiguousCached != null) {
-      return isAmbiguousCached;
+    if (mappedRanges.size() > 1) {
+      return true;
     }
-    if (!hasRetraceResult()) {
+    List<MappedRange> methodRanges = mappedRanges.get(0).getSecond();
+    if (methodRanges == null || methodRanges.isEmpty()) {
       return false;
     }
-    assert mappedRanges != null;
-    Range minifiedRange = null;
-    boolean seenNull = false;
-    for (MappedRange mappedRange : mappedRanges.getMappedRanges()) {
-      if (minifiedRange != null && !minifiedRange.equals(mappedRange.minifiedRange)) {
-        isAmbiguousCached = true;
+    MappedRange lastRange = methodRanges.get(0);
+    for (MappedRange mappedRange : methodRanges) {
+      if (mappedRange != lastRange
+          && (mappedRange.minifiedRange == null
+              || !mappedRange.minifiedRange.equals(lastRange.minifiedRange))) {
         return true;
-      } else if (minifiedRange == null) {
-        if (seenNull) {
-          isAmbiguousCached = true;
-          return true;
-        }
-        seenNull = true;
       }
-      minifiedRange = mappedRange.minifiedRange;
     }
-    isAmbiguousCached = false;
     return false;
   }
 
-  public RetraceMethodResult narrowByLine(int linePosition) {
-    if (!hasRetraceResult()) {
-      return this;
-    }
-    List<MappedRange> narrowedRanges = this.mappedRanges.allRangesForLine(linePosition, false);
-    if (narrowedRanges.isEmpty()) {
-      narrowedRanges = new ArrayList<>();
-      for (MappedRange mappedRange : this.mappedRanges.getMappedRanges()) {
-        if (mappedRange.minifiedRange == null) {
-          narrowedRanges.add(mappedRange);
+  public RetraceFrameResult narrowByPosition(int position) {
+    List<Pair<RetraceClassResult.Element, List<MappedRange>>> narrowedRanges = new ArrayList<>();
+    List<Pair<RetraceClassResult.Element, List<MappedRange>>> noMappingRanges = new ArrayList<>();
+    for (Pair<RetraceClassResult.Element, List<MappedRange>> mappedRange : mappedRanges) {
+      if (mappedRange.getSecond() == null) {
+        noMappingRanges.add(new Pair<>(mappedRange.getFirst(), null));
+        continue;
+      }
+      List<MappedRange> ranges =
+          new MappedRangesOfName(mappedRange.getSecond()).allRangesForLine(position, false);
+      boolean hasAddedRanges = false;
+      if (!ranges.isEmpty()) {
+        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ranges));
+        hasAddedRanges = true;
+      } else {
+        narrowedRanges = new ArrayList<>();
+        for (MappedRange mapped : mappedRange.getSecond()) {
+          if (mapped.minifiedRange == null) {
+            narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ImmutableList.of(mapped)));
+            hasAddedRanges = true;
+          }
         }
       }
+      if (!hasAddedRanges) {
+        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), null));
+      }
     }
-    return new RetraceMethodResult(
-        classElement, new MappedRangesOfName(narrowedRanges), obfuscatedName, retracer);
+    return new RetraceFrameResult(
+        classResult,
+        narrowedRanges.isEmpty() ? noMappingRanges : narrowedRanges,
+        methodDefinition,
+        position,
+        retracer);
   }
 
   @Override
   public Stream<Element> stream() {
-    if (!hasRetraceResult()) {
-      return Stream.of(new Element(this, classElement, getUnknownReference(), null));
-    }
-    return mappedRanges.getMappedRanges().stream()
-        .map(
-            mappedRange -> {
-              MethodSignature signature = mappedRange.signature;
-              RetracedClass holder =
-                  mappedRange.signature.isQualified()
-                      ? RetracedClass.create(
-                          Reference.classFromDescriptor(
-                              DescriptorUtils.javaTypeToDescriptor(
-                                  mappedRange.signature.toHolderFromQualified())))
-                      : classElement.getRetracedClass();
-              List<TypeReference> formalTypes = new ArrayList<>(signature.parameters.length);
-              for (String parameter : signature.parameters) {
-                formalTypes.add(Reference.typeFromTypeName(parameter));
+    return mappedRanges.stream()
+        .flatMap(
+            mappedRangePair -> {
+              RetraceClassResult.Element classElement = mappedRangePair.getFirst();
+              List<MappedRange> mappedRanges = mappedRangePair.getSecond();
+              if (mappedRanges == null || mappedRanges.isEmpty()) {
+                return Stream.of(
+                    new Element(
+                        this,
+                        classElement,
+                        RetracedMethod.create(
+                            methodDefinition.substituteHolder(
+                                classElement.getRetracedClass().getClassReference()))));
               }
-              TypeReference returnType =
-                  Reference.returnTypeFromDescriptor(
-                      DescriptorUtils.javaTypeToDescriptor(signature.type));
-              RetracedMethod retracedMethod =
-                  RetracedMethod.create(
-                      holder,
-                      Reference.method(
-                          holder.getClassReference(),
-                          signature.isQualified() ? signature.toUnqualifiedName() : signature.name,
-                          formalTypes,
-                          returnType));
-              return new Element(this, classElement, retracedMethod, mappedRange);
+              return mappedRanges.stream()
+                  .map(
+                      mappedRange -> {
+                        MethodReference methodReference =
+                            RetraceUtils.methodReferenceFromMappedRange(
+                                mappedRange, classElement.getRetracedClass().getClassReference());
+                        return new Element(
+                            this, classElement, RetracedMethod.create(methodReference));
+                      });
             });
   }
 
@@ -131,41 +126,28 @@
     return this;
   }
 
-  public static class Element {
+  public static class Element implements RetraceClassMemberElement<RetracedMethod> {
 
     private final RetracedMethod methodReference;
     private final RetraceMethodResult retraceMethodResult;
     private final RetraceClassResult.Element classElement;
-    private final MappedRange mappedRange;
 
     private Element(
         RetraceMethodResult retraceMethodResult,
         RetraceClassResult.Element classElement,
-        RetracedMethod methodReference,
-        MappedRange mappedRange) {
+        RetracedMethod methodReference) {
       this.classElement = classElement;
       this.retraceMethodResult = retraceMethodResult;
       this.methodReference = methodReference;
-      this.mappedRange = mappedRange;
     }
 
+    @Override
     public boolean isUnknown() {
       return methodReference.isUnknown();
     }
 
-    public boolean hasNoLineNumberRange() {
-      return mappedRange == null || mappedRange.minifiedRange == null;
-    }
-
-    public boolean containsMinifiedLineNumber(int linePosition) {
-      if (hasNoLineNumberRange()) {
-        return false;
-      }
-      return mappedRange.minifiedRange.from <= linePosition
-          && linePosition <= mappedRange.minifiedRange.to;
-    }
-
-    public RetracedMethod getMethod() {
+    @Override
+    public RetracedMethod getMember() {
       return methodReference;
     }
 
@@ -173,25 +155,20 @@
       return retraceMethodResult;
     }
 
+    @Override
     public RetraceClassResult.Element getClassElement() {
       return classElement;
     }
 
-    public int getOriginalLineNumber(int linePosition) {
-      return mappedRange != null ? mappedRange.getOriginalLineNumber(linePosition) : linePosition;
+    @Override
+    public void visitFrames(BiConsumer<RetracedMethod, Integer> consumer) {
+      consumer.accept(methodReference, 0);
     }
 
-    public int getFirstLineNumberOfOriginalRange() {
-      if (hasNoLineNumberRange()) {
-        return 0;
-      }
-      return mappedRange.getFirstLineNumberOfOriginalRange();
-    }
-
-    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+    @Override
+    public RetraceSourceFileResult retraceSourceFile(RetracedClassMember frame, String 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 8606324..910db1a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.retrace.RetraceUtils.methodDescriptionFromRetraceMethod;
+
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
@@ -14,7 +16,6 @@
 import com.android.tools.r8.retrace.RetracedField.KnownRetracedField;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
@@ -133,7 +134,7 @@
               case CLASS:
                 return line.getClassContext().getRetracedClass().getTypeName();
               case METHOD:
-                return line.getMethodContext().getMethod().getMethodName();
+                return line.getMethodContext().getMember().getMethodName();
               case SOURCE:
                 return line.getSource();
               case LINE:
@@ -241,9 +242,10 @@
 
   static class RetraceStringContext {
     private final Element classContext;
-    private final RetracedClass qualifiedContext;
+    private final RetraceClassMemberElement<? extends RetracedMethod> methodContext;
+    private final RetracedClass qualifiedClassContext;
+    private final RetracedMethod qualifiedMethodContext;
     private final String methodName;
-    private final RetraceMethodResult.Element methodContext;
     private final int minifiedLineNumber;
     private final int originalLineNumber;
     private final String source;
@@ -251,17 +253,19 @@
 
     private RetraceStringContext(
         Element classContext,
-        RetracedClass qualifiedContext,
+        RetraceClassMemberElement<? extends RetracedMethod> methodContext,
+        RetracedClass qualifiedClassContext,
+        RetracedMethod qualifiedMethodContext,
         String methodName,
-        RetraceMethodResult.Element methodContext,
         int minifiedLineNumber,
         int originalLineNumber,
         String source,
         boolean isAmbiguous) {
       this.classContext = classContext;
-      this.qualifiedContext = qualifiedContext;
-      this.methodName = methodName;
       this.methodContext = methodContext;
+      this.qualifiedClassContext = qualifiedClassContext;
+      this.qualifiedMethodContext = qualifiedMethodContext;
+      this.methodName = methodName;
       this.minifiedLineNumber = minifiedLineNumber;
       this.originalLineNumber = originalLineNumber;
       this.source = source;
@@ -269,16 +273,18 @@
     }
 
     private static RetraceStringContext empty() {
-      return new RetraceStringContext(null, null, null, null, NO_MATCH, NO_MATCH, null, false);
+      return new RetraceStringContext(
+          null, null, null, null, null, NO_MATCH, NO_MATCH, null, false);
     }
 
     private RetraceStringContext withClassContext(
         Element classContext, RetracedClass qualifiedContext) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -288,9 +294,10 @@
     private RetraceStringContext withMethodName(String methodName) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -298,26 +305,39 @@
     }
 
     private RetraceStringContext withMethodContext(
-        RetraceMethodResult.Element methodContext,
-        RetracedClass qualifiedContext,
-        boolean isAmbiguous) {
+        RetraceClassMemberElement<? extends RetracedMethod> methodContext, boolean isAmbiguous) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
           isAmbiguous);
     }
 
-    private RetraceStringContext withQualifiedContext(RetracedClass qualifiedContext) {
+    private RetraceStringContext withQualifiedClassContext(RetracedClass qualifiedContext) {
       return new RetraceStringContext(
           classContext,
+          methodContext,
+          qualifiedContext,
+          qualifiedMethodContext,
+          methodName,
+          minifiedLineNumber,
+          originalLineNumber,
+          source,
+          isAmbiguous);
+    }
+
+    private RetraceStringContext withQualifiedMethodContext(RetracedMethod qualifiedContext) {
+      return new RetraceStringContext(
+          classContext,
+          methodContext,
+          qualifiedContext.getHolderClass(),
           qualifiedContext,
           methodName,
-          methodContext,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -327,9 +347,10 @@
     public RetraceStringContext withSource(String source) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -339,9 +360,10 @@
     public RetraceStringContext withLineNumbers(int minifiedLineNumber, int originalLineNumber) {
       return new RetraceStringContext(
           classContext,
-          qualifiedContext,
-          methodName,
           methodContext,
+          qualifiedClassContext,
+          qualifiedMethodContext,
+          methodName,
           minifiedLineNumber,
           originalLineNumber,
           source,
@@ -385,7 +407,7 @@
       return context.classContext;
     }
 
-    private RetraceMethodResult.Element getMethodContext() {
+    private RetraceClassMemberElement<? extends RetracedMethod> getMethodContext() {
       return context.methodContext;
     }
 
@@ -523,7 +545,7 @@
         for (RetraceString retraceString : strings) {
           retraceResult.forEach(
               element -> {
-                final RetraceString newRetraceString =
+                RetraceString newRetraceString =
                     retraceString.updateContext(
                         context -> context.withClassContext(element, element.getRetracedClass()));
                 retraceStrings.add(newRetraceString);
@@ -657,7 +679,7 @@
             if (classNameGroupHandler != null) {
               for (RetraceString string : strings) {
                 classNameGroupHandler.commitClassName(
-                    original, string, string.context.qualifiedContext, matcher);
+                    original, string, string.context.qualifiedClassContext, matcher);
               }
             }
             return strings;
@@ -669,20 +691,23 @@
                 retraceString,
                 methodName,
                 (element, newContext) -> {
-                  final RetraceString newRetraceString = retraceString.duplicate(newContext);
-                  if (classNameGroupHandler != null) {
-                    classNameGroupHandler.commitClassName(
-                        original, newRetraceString, element.getMethod().getHolderClass(), matcher);
-                  }
-                  retracedStrings.add(
-                      newRetraceString.appendRetracedString(
-                          original,
-                          printVerbose
-                              ? RetraceUtils.methodDescriptionFromMethodReference(
-                                  element.getMethod(), false, true)
-                              : element.getMethod().getMethodName(),
-                          startOfGroup,
-                          matcher.end(captureGroup)));
+                  element.visitFrames(
+                      (method, ignoredPosition) -> {
+                        RetraceString newRetraceString =
+                            retraceString.duplicate(newContext.withQualifiedMethodContext(method));
+                        if (classNameGroupHandler != null) {
+                          classNameGroupHandler.commitClassName(
+                              original, newRetraceString, method.getHolderClass(), matcher);
+                        }
+                        retracedStrings.add(
+                            newRetraceString.appendRetracedString(
+                                original,
+                                printVerbose
+                                    ? methodDescriptionFromRetraceMethod(method, false, true)
+                                    : method.getMethodName(),
+                                startOfGroup,
+                                matcher.end(captureGroup)));
+                      });
                 });
           }
           return retracedStrings;
@@ -703,24 +728,25 @@
     private static void retraceMethodForString(
         RetraceString retraceString,
         String methodName,
-        BiConsumer<RetraceMethodResult.Element, RetraceStringContext> process) {
+        BiConsumer<RetraceClassMemberElement<? extends RetracedMethod>, RetraceStringContext>
+            process) {
       if (retraceString.context.classContext == null) {
         return;
       }
       RetraceMethodResult retraceMethodResult =
           retraceString.getClassContext().lookupMethod(methodName);
+      Result<? extends RetraceClassMemberElement<RetracedMethod>, ?> retraceResult;
       if (retraceString.context.minifiedLineNumber > NO_MATCH) {
-        retraceMethodResult =
-            retraceMethodResult.narrowByLine(retraceString.context.minifiedLineNumber);
+        retraceResult =
+            retraceMethodResult.narrowByPosition(retraceString.context.minifiedLineNumber);
+      } else {
+        retraceResult = retraceMethodResult;
       }
-      retraceMethodResult.forEach(
+      retraceResult.forEach(
           element ->
               process.accept(
                   element,
-                  retraceString.context.withMethodContext(
-                      element,
-                      element.getMethod().getHolderClass(),
-                      element.getRetraceMethodResult().isAmbiguous())));
+                  retraceString.context.withMethodContext(element, retraceResult.isAmbiguous())));
     }
   }
 
@@ -756,12 +782,12 @@
             if (classNameGroupHandler != null) {
               for (RetraceString string : strings) {
                 classNameGroupHandler.commitClassName(
-                    original, string, string.context.qualifiedContext, matcher);
+                    original, string, string.context.qualifiedClassContext, matcher);
               }
             }
             return strings;
           }
-          String methodName = matcher.group(captureGroup);
+          String fieldName = matcher.group(captureGroup);
           List<RetraceString> retracedStrings = new ArrayList<>();
           for (RetraceString retraceString : strings) {
             if (retraceString.getClassContext() == null) {
@@ -769,7 +795,7 @@
               return strings;
             }
             final RetraceFieldResult retraceFieldResult =
-                retraceString.getClassContext().lookupField(methodName);
+                retraceString.getClassContext().lookupField(fieldName);
             assert !retraceFieldResult.isAmbiguous();
             retraceFieldResult.forEach(
                 element -> {
@@ -781,7 +807,8 @@
                       retraceString
                           .updateContext(
                               context ->
-                                  context.withQualifiedContext(element.getField().getHolderClass()))
+                                  context.withQualifiedClassContext(
+                                      element.getField().getHolderClass()))
                           .appendRetracedString(
                               original,
                               getFieldString(element.getField()),
@@ -829,10 +856,12 @@
           }
           RetraceSourceFileResult sourceFileResult =
               retraceString.getMethodContext() != null
-                  ? retraceString.getMethodContext().retraceSourceFile(fileName)
+                  ? retraceString
+                      .getMethodContext()
+                      .retraceSourceFile(retraceString.context.qualifiedMethodContext, fileName)
                   : RetraceUtils.getSourceFile(
                       retraceString.getClassContext(),
-                      retraceString.context.qualifiedContext,
+                      retraceString.context.qualifiedClassContext,
                       fileName,
                       retracer);
           retracedStrings.add(
@@ -873,7 +902,8 @@
           int lineNumber = Integer.parseInt(lineNumberAsString);
           List<RetraceString> retracedStrings = new ArrayList<>();
           for (RetraceString retraceString : strings) {
-            RetraceMethodResult.Element methodContext = retraceString.context.methodContext;
+            RetraceClassMemberElement<? extends RetracedMethod> methodContext =
+                retraceString.context.methodContext;
             if (methodContext == null) {
               if (retraceString.context.classContext == null
                   || retraceString.context.methodName == null) {
@@ -889,34 +919,33 @@
                   (element, newContext) -> {
                     // The same method can be represented multiple times if it has multiple
                     // mappings.
-                    if (element.hasNoLineNumberRange()
-                        || !element.containsMinifiedLineNumber(lineNumber)) {
-                      diagnosticsHandler.info(
-                          new StringDiagnostic(
-                              "Pruning "
-                                  + retraceString.builder.retracedString.toString()
-                                  + " from result because method is not in range on line number "
-                                  + lineNumber));
-                    }
-                    final int originalLineNumber = element.getOriginalLineNumber(lineNumber);
-                    retracedStrings.add(
-                        retraceString
-                            .updateContext(
-                                context -> context.withLineNumbers(lineNumber, originalLineNumber))
-                            .appendRetracedString(
-                                original,
-                                originalLineNumber + "",
-                                startOfGroup,
-                                matcher.end(captureGroup)));
+                    element.visitFrames(
+                        (method, ignoredPosition) -> {
+                          int originalPosition = method.getOriginalPositionOrDefault(lineNumber);
+                          retracedStrings.add(
+                              retraceString
+                                  .duplicate(
+                                      retraceString
+                                          .context
+                                          .withQualifiedMethodContext(method)
+                                          .withLineNumbers(lineNumber, originalPosition))
+                                  .appendRetracedString(
+                                      original,
+                                      originalPosition + "",
+                                      startOfGroup,
+                                      matcher.end(captureGroup)));
+                        });
                   });
               continue;
             }
             // If the method context is unknown, do nothing.
-            if (methodContext.isUnknown() || methodContext.hasNoLineNumberRange()) {
+            if (methodContext.isUnknown()) {
               retracedStrings.add(retraceString);
               continue;
             }
-            int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber);
+            int originalLineNumber =
+                retraceString.context.qualifiedMethodContext.getOriginalPositionOrDefault(
+                    lineNumber);
             retracedStrings.add(
                 retraceString
                     .updateContext(
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
deleted file mode 100644
index e2e3616..0000000
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ /dev/null
@@ -1,625 +0,0 @@
-// Copyright (c) 2019, 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;
-
-import static com.android.tools.r8.retrace.RetraceUtils.methodDescriptionFromMethodReference;
-import static com.google.common.base.Predicates.not;
-
-import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Predicate;
-
-public final class RetraceStackTrace {
-
-  public static class StackTraceNode {
-
-    private final List<StackTraceLine> lines;
-
-    StackTraceNode(List<StackTraceLine> lines) {
-      this.lines = lines;
-      assert !lines.isEmpty();
-      assert lines.size() == 1 || lines.stream().allMatch(StackTraceLine::isAtLine);
-    }
-
-    public void append(List<String> strings) {
-      assert !lines.isEmpty();
-      if (lines.size() == 1) {
-        strings.add(lines.get(0).toString());
-        return;
-      }
-      // We must have an inlining or ambiguous match here, thus all lines are at-lines.
-      assert lines.stream().allMatch(StackTraceLine::isAtLine);
-      assert lines.stream()
-          .allMatch(line -> line.asAtLine().isAmbiguous == lines.get(0).asAtLine().isAmbiguous);
-      if (lines.get(0).asAtLine().isAmbiguous) {
-        lines.sort(new AtStackTraceLineComparator());
-      }
-      boolean shouldPrintOr = false;
-      for (StackTraceLine line : lines) {
-        assert line.isAtLine();
-        AtLine atLine = line.asAtLine();
-        if (atLine.isAmbiguous && shouldPrintOr) {
-          String atLineString = atLine.toString();
-          int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(atLineString);
-          strings.add(
-              atLineString.substring(0, firstNonWhitespaceCharacter)
-                  + "<OR> "
-                  + atLineString.substring(firstNonWhitespaceCharacter));
-        } else {
-          strings.add(atLine.toString());
-        }
-        shouldPrintOr = true;
-      }
-    }
-  }
-
-  static class AtStackTraceLineComparator extends AmbiguousComparator<StackTraceLine> {
-
-    AtStackTraceLineComparator() {
-      super(
-          (line, t) -> {
-            assert line.isAtLine();
-            AtLine atLine = line.asAtLine();
-            switch (t) {
-              case CLASS:
-                return atLine.clazz;
-              case METHOD:
-                return atLine.method;
-              case SOURCE:
-                return atLine.fileName;
-              case LINE:
-                return atLine.linePosition + "";
-              default:
-                assert false;
-            }
-            throw new RuntimeException("Comparator key is unknown");
-          });
-    }
-  }
-
-  private final RetraceApi retracer;
-  private final List<String> stackTrace;
-  private final DiagnosticsHandler diagnosticsHandler;
-  private final boolean verbose;
-
-  RetraceStackTrace(
-      RetraceApi retracer,
-      List<String> stackTrace,
-      DiagnosticsHandler diagnosticsHandler,
-      boolean verbose) {
-    this.retracer = retracer;
-    this.stackTrace = stackTrace;
-    this.diagnosticsHandler = diagnosticsHandler;
-    this.verbose = verbose;
-  }
-
-  public RetraceCommandLineResult retrace() {
-    ArrayList<StackTraceNode> result = new ArrayList<>();
-    retraceLine(stackTrace, 0, result);
-    List<String> retracedStrings = new ArrayList<>();
-    for (StackTraceNode node : result) {
-      node.append(retracedStrings);
-    }
-    return new RetraceCommandLineResult(retracedStrings);
-  }
-
-  private void retraceLine(List<String> stackTrace, int index, List<StackTraceNode> result) {
-    if (stackTrace.size() <= index) {
-      return;
-    }
-    StackTraceLine stackTraceLine = parseLine(index + 1, stackTrace.get(index));
-    List<StackTraceLine> retraced = stackTraceLine.retrace(retracer, verbose);
-    StackTraceNode node = new StackTraceNode(retraced);
-    result.add(node);
-    retraceLine(stackTrace, index + 1, result);
-  }
-
-  abstract static class StackTraceLine {
-
-    abstract List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose);
-
-    static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
-      return firstFromIndex(line, index, not(Character::isWhitespace));
-    }
-
-    static int firstCharFromIndex(String line, int index, char ch) {
-      return firstFromIndex(line, index, c -> c == ch);
-    }
-
-    static int firstFromIndex(String line, int index, Predicate<Character> predicate) {
-      for (int i = index; i < line.length(); i++) {
-        if (predicate.test(line.charAt(i))) {
-          return i;
-        }
-      }
-      return line.length();
-    }
-
-    boolean isAtLine() {
-      return false;
-    }
-
-    AtLine asAtLine() {
-      return null;
-    }
-
-    boolean isExceptionLine() {
-      return false;
-    }
-
-    ExceptionLine asExceptionLine() {
-      return null;
-    }
-  }
-
-  /**
-   * Captures a stack trace line of the following formats:
-   *
-   * <ul>
-   *   <li>com.android.r8.R8Exception
-   *   <li>com.android.r8.R8Exception: Problem when compiling program
-   *   <li>Caused by: com.android.r8.R8InnerException: You have to write the program first
-   *   <li>com.android.r8.R8InnerException: You have to write the program first
-   * </ul>
-   *
-   * <p>This will also contains false positives, such as
-   *
-   * <pre>
-   *   W( 8207) VFY: unable to resolve static method 11: Lprivateinterfacemethods/I$-CC;....
-   * </pre>
-   *
-   * <p>The only invalid chars for type-identifiers for a java type-name is ';', '[' and '/', so we
-   * cannot really disregard the above line.
-   *
-   * <p>Caused by and Suppressed seems to not change based on locale, so we use these as markers.
-   */
-  static class ExceptionLine extends StackTraceLine {
-
-    private static final String CAUSED_BY = "Caused by: ";
-    private static final String SUPPRESSED = "Suppressed: ";
-
-    private final String initialWhiteSpace;
-    private final String description;
-    private final String exceptionClass;
-    private final String message;
-
-    ExceptionLine(
-        String initialWhiteSpace, String description, String exceptionClass, String message) {
-      this.initialWhiteSpace = initialWhiteSpace;
-      this.description = description;
-      this.exceptionClass = exceptionClass;
-      this.message = message;
-    }
-
-    static ExceptionLine tryParse(String line) {
-      if (line.isEmpty()) {
-        return null;
-      }
-      int firstNonWhiteSpaceChar = firstNonWhiteSpaceCharacterFromIndex(line, 0);
-      String description = "";
-      if (line.startsWith(CAUSED_BY, firstNonWhiteSpaceChar)) {
-        description = CAUSED_BY;
-      } else if (line.startsWith(SUPPRESSED, firstNonWhiteSpaceChar)) {
-        description = SUPPRESSED;
-      }
-      int exceptionStartIndex = firstNonWhiteSpaceChar + description.length();
-      int messageStartIndex = firstCharFromIndex(line, exceptionStartIndex, ':');
-      String className = line.substring(exceptionStartIndex, messageStartIndex);
-      if (!DescriptorUtils.isValidJavaType(className)) {
-        return null;
-      }
-      return new ExceptionLine(
-          line.substring(0, firstNonWhiteSpaceChar),
-          description,
-          className,
-          line.substring(messageStartIndex));
-    }
-
-    @Override
-    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
-      List<StackTraceLine> exceptionLines = new ArrayList<>();
-      retracer
-          .retrace(Reference.classFromTypeName(exceptionClass))
-          .forEach(
-              element ->
-                  exceptionLines.add(
-                      new ExceptionLine(
-                          initialWhiteSpace,
-                          description,
-                          element.getRetracedClass().getTypeName(),
-                          message)));
-      return exceptionLines;
-    }
-
-    @Override
-    public String toString() {
-      return initialWhiteSpace + description + exceptionClass + message;
-    }
-
-    @Override
-    boolean isExceptionLine() {
-      return true;
-    }
-
-    @Override
-    ExceptionLine asExceptionLine() {
-      return this;
-    }
-  }
-
-  /**
-   * Captures a stack trace line on the following form
-   *
-   * <ul>
-   *   <li>at dalvik.system.NativeStart.main(NativeStart.java:99)
-   *   <li>at dalvik.system.NativeStart.main(:99)
-   *   <li>at dalvik.system.NativeStart.main(Foo.java:)
-   *   <li>at dalvik.system.NativeStart.main(Native Method)
-   *   <li>at classloader/named_module@version/foo.bar.baz(:20)
-   *   <li>at classloader//foo.bar.baz(:20)
-   * </ul>
-   *
-   * <p>Empirical evidence suggests that the "at" string is never localized.
-   */
-  static class AtLine extends StackTraceLine {
-
-    private static final int NO_POSITION = -2;
-    private static final int INVALID_POSITION = -1;
-
-    private final String startingWhitespace;
-    private final String at;
-    private final String classLoaderName;
-    private final String moduleName;
-    private final String clazz;
-    private final String method;
-    private final String methodAsString;
-    private final String fileName;
-    private final int linePosition;
-    private final boolean isAmbiguous;
-
-    private AtLine(
-        String startingWhitespace,
-        String at,
-        String classLoaderName,
-        String moduleName,
-        String clazz,
-        String method,
-        String methodAsString,
-        String fileName,
-        int linePosition,
-        boolean isAmbiguous) {
-      this.startingWhitespace = startingWhitespace;
-      this.at = at;
-      this.classLoaderName = classLoaderName;
-      this.moduleName = moduleName;
-      this.clazz = clazz;
-      this.method = method;
-      this.methodAsString = methodAsString;
-      this.fileName = fileName;
-      this.linePosition = linePosition;
-      this.isAmbiguous = isAmbiguous;
-    }
-
-    static AtLine tryParse(String line) {
-      // Check that the line is indented with some amount of white space.
-      if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
-        return null;
-      }
-      // Find the first non-white space character and check that we have the sequence 'a', 't', ' '.
-      int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
-      if (firstNonWhiteSpace + 2 >= line.length()
-          || line.charAt(firstNonWhiteSpace) != 'a'
-          || line.charAt(firstNonWhiteSpace + 1) != 't'
-          || line.charAt(firstNonWhiteSpace + 2) != ' ') {
-        return null;
-      }
-      int classClassLoaderOrModuleStartIndex =
-          firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
-      if (classClassLoaderOrModuleStartIndex >= line.length()
-          || classClassLoaderOrModuleStartIndex != firstNonWhiteSpace + 3) {
-        return null;
-      }
-      int parensStart = firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '(');
-      if (parensStart >= line.length()) {
-        return null;
-      }
-      int parensEnd = firstCharFromIndex(line, parensStart, ')');
-      if (parensEnd >= line.length()) {
-        return null;
-      }
-      if (firstNonWhiteSpaceCharacterFromIndex(line, parensEnd) == line.length()) {
-        return null;
-      }
-      int methodSeparator = line.lastIndexOf('.', parensStart);
-      if (methodSeparator <= classClassLoaderOrModuleStartIndex) {
-        return null;
-      }
-      // Check if we have a filename and position.
-      String fileName = "";
-      int position = NO_POSITION;
-      int separatorIndex = firstCharFromIndex(line, parensStart, ':');
-      if (separatorIndex < parensEnd) {
-        fileName = line.substring(parensStart + 1, separatorIndex);
-        try {
-          String positionAsString = line.substring(separatorIndex + 1, parensEnd);
-          position = Integer.parseInt(positionAsString);
-        } catch (NumberFormatException e) {
-          position = INVALID_POSITION;
-        }
-      } else {
-        fileName = line.substring(parensStart + 1, parensEnd);
-      }
-      String classLoaderName = null;
-      String moduleName = null;
-      int classStartIndex = classClassLoaderOrModuleStartIndex;
-      int classLoaderOrModuleEndIndex =
-          firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '/');
-      if (classLoaderOrModuleEndIndex < methodSeparator) {
-        int moduleEndIndex = firstCharFromIndex(line, classLoaderOrModuleEndIndex + 1, '/');
-        if (moduleEndIndex < methodSeparator) {
-          // The stack trace contains both a class loader and module
-          classLoaderName =
-              line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
-          moduleName = line.substring(classLoaderOrModuleEndIndex + 1, moduleEndIndex);
-          classStartIndex = moduleEndIndex + 1;
-        } else {
-          moduleName =
-              line.substring(classClassLoaderOrModuleStartIndex, classLoaderOrModuleEndIndex);
-          classStartIndex = classLoaderOrModuleEndIndex + 1;
-        }
-      }
-      String className = line.substring(classStartIndex, methodSeparator);
-      String methodName = line.substring(methodSeparator + 1, parensStart);
-      return new AtLine(
-          line.substring(0, firstNonWhiteSpace),
-          line.substring(firstNonWhiteSpace, classClassLoaderOrModuleStartIndex),
-          classLoaderName,
-          moduleName,
-          className,
-          methodName,
-          className + "." + methodName,
-          fileName,
-          position,
-          false);
-    }
-
-    private boolean hasLinePosition() {
-      return linePosition > -1;
-    }
-
-    @Override
-    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
-      List<StackTraceLine> lines = new ArrayList<>();
-      String retraceClassLoaderName = classLoaderName;
-      if (retraceClassLoaderName != null) {
-        ClassReference classLoaderReference = Reference.classFromTypeName(retraceClassLoaderName);
-        retracer
-            .retrace(classLoaderReference)
-            .forEach(
-                classElement -> {
-                  retraceClassAndMethods(
-                      retracer, verbose, lines, classElement.getRetracedClass().getTypeName());
-                });
-      } else {
-        retraceClassAndMethods(retracer, verbose, lines, retraceClassLoaderName);
-      }
-      return lines;
-    }
-
-    private void retraceClassAndMethods(
-        RetraceApi retracer, boolean verbose, List<StackTraceLine> lines, String classLoaderName) {
-      ClassReference classReference = Reference.classFromTypeName(clazz);
-      RetraceClassResult classResult = retracer.retrace(classReference);
-      RetraceMethodResult retraceResult = classResult.lookupMethod(method);
-      if (linePosition != NO_POSITION && linePosition != INVALID_POSITION) {
-        retraceResult = retraceResult.narrowByLine(linePosition);
-      }
-      retraceResult.forEach(
-          methodElement -> {
-            RetracedMethod methodReference = methodElement.getMethod();
-            lines.add(
-                new AtLine(
-                    startingWhitespace,
-                    at,
-                    classLoaderName,
-                    moduleName,
-                    methodReference.getHolderClass().getTypeName(),
-                    methodReference.getMethodName(),
-                    methodDescriptionFromMethodReference(methodReference, true, verbose),
-                    methodElement.retraceSourceFile(fileName).getFilename(),
-                    hasLinePosition()
-                        ? methodElement.getOriginalLineNumber(linePosition)
-                        : linePosition,
-                    methodElement.getRetraceMethodResult().isAmbiguous()));
-          });
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder sb = new StringBuilder(startingWhitespace);
-      sb.append(at);
-      if (classLoaderName != null) {
-        sb.append(classLoaderName);
-        sb.append("/");
-      }
-      if (moduleName != null) {
-        sb.append(moduleName);
-        sb.append("/");
-      }
-      sb.append(methodAsString);
-      sb.append("(");
-      sb.append(fileName);
-      if (linePosition != NO_POSITION) {
-        sb.append(":");
-      }
-      if (linePosition > INVALID_POSITION) {
-        sb.append(linePosition);
-      }
-      sb.append(")");
-      return sb.toString();
-    }
-
-    @Override
-    boolean isAtLine() {
-      return true;
-    }
-
-    @Override
-    AtLine asAtLine() {
-      return this;
-    }
-  }
-
-  static class MoreLine extends StackTraceLine {
-    private final String line;
-
-    MoreLine(String line) {
-      this.line = line;
-    }
-
-    static StackTraceLine tryParse(String line) {
-      int dotsSeen = 0;
-      boolean isWhiteSpaceAllowed = true;
-      for (int i = 0; i < line.length(); i++) {
-        char ch = line.charAt(i);
-        if (Character.isWhitespace(ch) && isWhiteSpaceAllowed) {
-          continue;
-        }
-        isWhiteSpaceAllowed = false;
-        if (ch != '.') {
-          return null;
-        }
-        if (++dotsSeen == 3) {
-          return new MoreLine(line);
-        }
-      }
-      return null;
-    }
-
-    @Override
-    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
-      return ImmutableList.of(new MoreLine(line));
-    }
-
-    @Override
-    public String toString() {
-      return line;
-    }
-  }
-
-  static class CircularReferenceLine extends StackTraceLine {
-
-    private final String startWhitespace;
-    private final String exceptionClass;
-    private final String endBracketAndWhitespace;
-
-    private static final String CIRCULAR_REFERENCE = "[CIRCULAR REFERENCE:";
-
-    public CircularReferenceLine(
-        String startWhitespace, String exceptionClass, String endBracketAndWhitespace) {
-      this.startWhitespace = startWhitespace;
-      this.exceptionClass = exceptionClass;
-      this.endBracketAndWhitespace = endBracketAndWhitespace;
-    }
-
-    static StackTraceLine tryParse(String line) {
-      // Check that the line is indented with some amount of white space.
-      if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
-        return null;
-      }
-      // Find the first non-white space character and check that we have the sequence
-      // '[CIRCULAR REFERENCE:Exception]'.
-      int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
-      if (!line.startsWith(CIRCULAR_REFERENCE, firstNonWhiteSpace)) {
-        return null;
-      }
-      int exceptionStartIndex = firstNonWhiteSpace + CIRCULAR_REFERENCE.length();
-      int lastBracketPosition = firstCharFromIndex(line, exceptionStartIndex, ']');
-      if (lastBracketPosition == line.length()) {
-        return null;
-      }
-      int onlyWhitespaceFromLastBracket =
-          firstNonWhiteSpaceCharacterFromIndex(line, lastBracketPosition + 1);
-      if (onlyWhitespaceFromLastBracket != line.length()) {
-        return null;
-      }
-      return new CircularReferenceLine(
-          line.substring(0, firstNonWhiteSpace),
-          line.substring(exceptionStartIndex, lastBracketPosition),
-          line.substring(lastBracketPosition));
-    }
-
-    @Override
-    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
-      List<StackTraceLine> exceptionLines = new ArrayList<>();
-      retracer
-          .retrace(Reference.classFromTypeName(exceptionClass))
-          .forEach(
-              element ->
-                  exceptionLines.add(
-                      new CircularReferenceLine(
-                          startWhitespace,
-                          element.getRetracedClass().getTypeName(),
-                          endBracketAndWhitespace)));
-      return exceptionLines;
-    }
-
-    @Override
-    public String toString() {
-      return startWhitespace + CIRCULAR_REFERENCE + exceptionClass + endBracketAndWhitespace;
-    }
-  }
-
-  static class UnknownLine extends StackTraceLine {
-    private final String line;
-
-    UnknownLine(String line) {
-      this.line = line;
-    }
-
-    @Override
-    List<StackTraceLine> retrace(RetraceApi retracer, boolean verbose) {
-      return ImmutableList.of(new UnknownLine(line));
-    }
-
-    @Override
-    public String toString() {
-      return line;
-    }
-  }
-
-  private StackTraceLine parseLine(int lineNumber, String line) {
-    if (line == null) {
-      diagnosticsHandler.error(RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
-      throw new Retrace.RetraceAbortException();
-    }
-    // Most lines are 'at lines' so attempt to parse it first.
-    StackTraceLine parsedLine = AtLine.tryParse(line);
-    if (parsedLine != null) {
-      return parsedLine;
-    }
-    parsedLine = ExceptionLine.tryParse(line);
-    if (parsedLine != null) {
-      return parsedLine;
-    }
-    parsedLine = CircularReferenceLine.tryParse(line);
-    if (parsedLine != null) {
-      return parsedLine;
-    }
-    parsedLine = MoreLine.tryParse(line);
-    if (parsedLine == null) {
-      diagnosticsHandler.warning(
-          RetraceInvalidStackTraceLineDiagnostics.createParse(lineNumber, line));
-    }
-    parsedLine = new UnknownLine(line);
-    return parsedLine;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
index 4f7ec0a..d546d11 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceTypeResult.java
@@ -14,11 +14,15 @@
   private final TypeReference obfuscatedType;
   private final RetraceApi retracer;
 
-  RetraceTypeResult(TypeReference obfuscatedType, RetraceApi retracer) {
+  private RetraceTypeResult(TypeReference obfuscatedType, RetraceApi retracer) {
     this.obfuscatedType = obfuscatedType;
     this.retracer = retracer;
   }
 
+  static RetraceTypeResult create(TypeReference obfuscatedType, RetraceApi retracer) {
+    return new RetraceTypeResult(obfuscatedType, retracer);
+  }
+
   @Override
   public Stream<Element> stream() {
     // Handle void and primitive types as single element results.
@@ -36,6 +40,7 @@
         .map(classElement -> new Element(classElement.getRetracedClass().getRetracedType()));
   }
 
+  @Override
   public boolean isAmbiguous() {
     return false;
   }
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 335059e..e402a80 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -4,11 +4,19 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetracedMethod.KnownRetracedMethod;
 import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.Sets;
 import com.google.common.io.Files;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 public class RetraceUtils {
@@ -16,7 +24,7 @@
   private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
       Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source", "PG");
 
-  public static String methodDescriptionFromMethodReference(
+  public static String methodDescriptionFromRetraceMethod(
       RetracedMethod methodReference, boolean appendHolder, boolean verbose) {
     StringBuilder sb = new StringBuilder();
     if (appendHolder) {
@@ -115,4 +123,25 @@
     String newFileName = getClassSimpleName(retracedClassName);
     return newFileName + "." + extension;
   }
+
+  static MethodReference methodReferenceFromMappedRange(
+      MappedRange mappedRange, ClassReference classReference) {
+    MethodSignature signature = mappedRange.signature;
+    ClassReference holder =
+        signature.isQualified()
+            ? Reference.classFromDescriptor(
+                DescriptorUtils.javaTypeToDescriptor(signature.toHolderFromQualified()))
+            : classReference;
+    List<TypeReference> formalTypes = new ArrayList<>(signature.parameters.length);
+    for (String parameter : signature.parameters) {
+      formalTypes.add(Reference.typeFromTypeName(parameter));
+    }
+    TypeReference returnType =
+        Reference.returnTypeFromDescriptor(DescriptorUtils.javaTypeToDescriptor(signature.type));
+    return Reference.method(
+        holder,
+        signature.isQualified() ? signature.toUnqualifiedName() : signature.name,
+        formalTypes,
+        returnType);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
new file mode 100644
index 0000000..e05be47
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedClassMember.java
@@ -0,0 +1,10 @@
+// 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;
+
+public interface RetracedClassMember {
+
+  RetracedClass getHolderClass();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedField.java b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
index b809cab..132d0c7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedField.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedField.java
@@ -10,7 +10,7 @@
 import java.util.Objects;
 
 @Keep
-public abstract class RetracedField {
+public abstract class RetracedField implements RetracedClassMember {
 
   private RetracedField() {}
 
@@ -26,17 +26,13 @@
     return null;
   }
 
-  public abstract RetracedClass getHolderClass();
-
   public abstract String getFieldName();
 
   public static final class KnownRetracedField extends RetracedField {
 
-    private final RetracedClass classReference;
     private final FieldReference fieldReference;
 
-    private KnownRetracedField(RetracedClass classReference, FieldReference fieldReference) {
-      this.classReference = classReference;
+    private KnownRetracedField(FieldReference fieldReference) {
       this.fieldReference = fieldReference;
     }
 
@@ -52,7 +48,7 @@
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(fieldReference.getHolderClass());
     }
 
     @Override
@@ -77,43 +73,56 @@
         return false;
       }
       KnownRetracedField that = (KnownRetracedField) o;
-      assert !fieldReference.equals(that.fieldReference)
-          || classReference.equals(that.classReference);
       return fieldReference.equals(that.fieldReference);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(classReference, fieldReference);
+      return Objects.hash(fieldReference);
     }
   }
 
   public static final class UnknownRetracedField extends RetracedField {
 
-    private final RetracedClass classReference;
-    private final String name;
+    private final FieldDefinition fieldDefinition;
 
-    private UnknownRetracedField(RetracedClass classReference, String name) {
-      this.classReference = classReference;
-      this.name = name;
+    private UnknownRetracedField(FieldDefinition fieldDefinition) {
+      this.fieldDefinition = fieldDefinition;
     }
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(fieldDefinition.getHolderClass());
     }
 
     @Override
     public String getFieldName() {
-      return name;
+      return fieldDefinition.getName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      UnknownRetracedField that = (UnknownRetracedField) o;
+      return fieldDefinition.equals(that.fieldDefinition);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(fieldDefinition);
     }
   }
 
-  static RetracedField create(RetracedClass classReference, FieldReference fieldReference) {
-    return new KnownRetracedField(classReference, fieldReference);
+  static RetracedField create(FieldReference fieldReference) {
+    return new KnownRetracedField(fieldReference);
   }
 
-  static RetracedField createUnknown(RetracedClass classReference, String name) {
-    return new UnknownRetracedField(classReference, name);
+  static RetracedField create(FieldDefinition fieldDefinition) {
+    return new UnknownRetracedField(fieldDefinition);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
index ee3b792..937ded1 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedMethod.java
@@ -9,9 +9,12 @@
 import com.android.tools.r8.references.TypeReference;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 
 @Keep
-public abstract class RetracedMethod {
+public abstract class RetracedMethod implements RetracedClassMember {
+
+  private static final int NO_POSITION = -1;
 
   private RetracedMethod() {}
 
@@ -27,19 +30,21 @@
     return null;
   }
 
-  public abstract RetracedClass getHolderClass();
-
   public abstract String getMethodName();
 
+  public abstract boolean hasPosition();
+
+  public abstract int getOriginalPositionOrDefault(int defaultPosition);
+
   public static final class KnownRetracedMethod extends RetracedMethod {
 
     private final MethodReference methodReference;
-    private final RetracedClass classReference;
+    private final int position;
 
-    private KnownRetracedMethod(RetracedClass classReference, MethodReference methodReference) {
+    private KnownRetracedMethod(MethodReference methodReference, int position) {
       assert methodReference != null;
-      this.classReference = classReference;
       this.methodReference = methodReference;
+      this.position = position;
     }
 
     @Override
@@ -58,7 +63,7 @@
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(methodReference.getHolderClass());
     }
 
     @Override
@@ -66,6 +71,16 @@
       return methodReference.getMethodName();
     }
 
+    @Override
+    public boolean hasPosition() {
+      return position != NO_POSITION;
+    }
+
+    @Override
+    public int getOriginalPositionOrDefault(int defaultPosition) {
+      return hasPosition() ? position : defaultPosition;
+    }
+
     public TypeReference getReturnType() {
       assert !isVoid();
       return methodReference.getReturnType();
@@ -75,14 +90,10 @@
       return methodReference.getFormalTypes();
     }
 
-    public MethodReference getClassReference() {
+    public MethodReference getMethodReference() {
       return methodReference;
     }
 
-    public boolean equalsMethodReference(MethodReference reference) {
-      return methodReference.equals(reference);
-    }
-
     @Override
     public boolean equals(Object o) {
       if (this == o) {
@@ -92,43 +103,70 @@
         return false;
       }
       KnownRetracedMethod that = (KnownRetracedMethod) o;
-      assert !methodReference.equals(that.methodReference)
-          || classReference.equals(that.classReference);
-      return methodReference.equals(that.methodReference);
+      return position == that.position && methodReference.equals(that.methodReference);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(methodReference, classReference);
+      return Objects.hash(methodReference, position);
     }
   }
 
   public static final class UnknownRetracedMethod extends RetracedMethod {
 
-    private final RetracedClass classReference;
-    private final String name;
+    private final MethodDefinition methodDefinition;
+    private final int position;
 
-    private UnknownRetracedMethod(RetracedClass classReference, String name) {
-      this.classReference = classReference;
-      this.name = name;
+    private UnknownRetracedMethod(MethodDefinition methodDefinition, int position) {
+      this.methodDefinition = methodDefinition;
+      this.position = position;
     }
 
     @Override
     public RetracedClass getHolderClass() {
-      return classReference;
+      return RetracedClass.create(methodDefinition.getHolderClass());
     }
 
     @Override
     public String getMethodName() {
-      return name;
+      return methodDefinition.getName();
+    }
+
+    @Override
+    public boolean hasPosition() {
+      return position != NO_POSITION;
+    }
+
+    @Override
+    public int getOriginalPositionOrDefault(int defaultPosition) {
+      return hasPosition() ? position : defaultPosition;
+    }
+
+    public Optional<MethodReference> getMethodReference() {
+      if (!methodDefinition.isFullMethodDefinition()) {
+        return Optional.empty();
+      }
+      return Optional.of(methodDefinition.asFullMethodDefinition().getMethodReference());
     }
   }
 
-  static RetracedMethod create(RetracedClass classReference, MethodReference methodReference) {
-    return new KnownRetracedMethod(classReference, methodReference);
+  static RetracedMethod create(MethodDefinition methodDefinition) {
+    return create(methodDefinition, NO_POSITION);
   }
 
-  static RetracedMethod createUnknown(RetracedClass classReference, String name) {
-    return new UnknownRetracedMethod(classReference, name);
+  static RetracedMethod create(MethodDefinition methodDefinition, int position) {
+    if (methodDefinition.isFullMethodDefinition()) {
+      return new KnownRetracedMethod(
+          methodDefinition.asFullMethodDefinition().getMethodReference(), position);
+    }
+    return new UnknownRetracedMethod(methodDefinition, position);
+  }
+
+  static RetracedMethod create(MethodReference methodReference) {
+    return create(methodReference, NO_POSITION);
+  }
+
+  static RetracedMethod create(MethodReference methodReference, int position) {
+    return new KnownRetracedMethod(methodReference, position);
   }
 }
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 583ca7c..279253c 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -50,6 +50,13 @@
   }
 
   @Override
+  public RetraceFrameResult retrace(MethodReference methodReference, int position) {
+    return retrace(methodReference.getHolderClass())
+        .lookupMethod(methodReference.getMethodName())
+        .narrowByPosition(position);
+  }
+
+  @Override
   public RetraceClassResult retrace(ClassReference classReference) {
     return RetraceClassResult.create(
         classReference, classNameMapper.getClassNaming(classReference.getTypeName()), this);
@@ -57,6 +64,6 @@
 
   @Override
   public RetraceTypeResult retrace(TypeReference typeReference) {
-    return new RetraceTypeResult(typeReference, this);
+    return RetraceTypeResult.create(typeReference, this);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
new file mode 100644
index 0000000..efaab05
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxy.java
@@ -0,0 +1,27 @@
+// 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;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public abstract class StackTraceElementProxy<T> {
+
+  public abstract boolean hasClassName();
+
+  public abstract boolean hasMethodName();
+
+  public abstract boolean hasFileName();
+
+  public abstract boolean hasLineNumber();
+
+  public abstract String className();
+
+  public abstract String methodName();
+
+  public abstract String fileName();
+
+  public abstract int lineNumber();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
new file mode 100644
index 0000000..042b1f8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
@@ -0,0 +1,195 @@
+// 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;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.Reference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+@Keep
+public class StackTraceElementProxyRetracer<T extends StackTraceElementProxy<?>> {
+
+  private final RetraceApi retracer;
+
+  public StackTraceElementProxyRetracer(RetraceApi retracer) {
+    this.retracer = retracer;
+  }
+
+  public Stream<RetraceStackTraceProxy<T>> retrace(T element) {
+    if (!element.hasClassName()) {
+      return Stream.of(RetraceStackTraceProxy.builder(element).build());
+    }
+    RetraceClassResult classResult =
+        retracer.retrace(Reference.classFromTypeName(element.className()));
+    List<RetraceStackTraceProxy<T>> retracedProxies = new ArrayList<>();
+    if (!element.hasMethodName()) {
+      classResult.forEach(
+          classElement -> {
+            RetraceStackTraceProxy.Builder<T> proxy =
+                RetraceStackTraceProxy.builder(element)
+                    .setRetracedClassElement(classElement.getRetracedClass())
+                    .setAmbiguous(classResult.isAmbiguous());
+            if (element.hasFileName()) {
+              proxy.setSourceFile(classElement.retraceSourceFile(element.fileName()).getFilename());
+            }
+            retracedProxies.add(proxy.build());
+          });
+    } else {
+      RetraceMethodResult retraceMethodResult = classResult.lookupMethod(element.methodName());
+      Result<? extends RetraceClassMemberElement<RetracedMethod>, ?> methodResult;
+      if (element.hasLineNumber()) {
+        methodResult = retraceMethodResult.narrowByPosition(element.lineNumber());
+      } else {
+        methodResult = retraceMethodResult;
+      }
+      methodResult.forEach(
+          methodElement -> {
+            methodElement.visitFrames(
+                (frame, index) -> {
+                  RetraceStackTraceProxy.Builder<T> proxy =
+                      RetraceStackTraceProxy.builder(element)
+                          .setRetracedClassElement(frame.getHolderClass())
+                          .setRetracedMethodElement(frame)
+                          .setAmbiguous(methodResult.isAmbiguous() && index == 0);
+                  if (element.hasLineNumber()) {
+                    proxy.setLineNumber(frame.getOriginalPositionOrDefault(element.lineNumber()));
+                  }
+                  if (element.hasFileName()) {
+                    proxy.setSourceFile(
+                        methodElement.retraceSourceFile(frame, element.fileName()).getFilename());
+                  }
+                  retracedProxies.add(proxy.build());
+                });
+          });
+    }
+    return retracedProxies.stream();
+  }
+
+  @Keep
+  public static class RetraceStackTraceProxy<T extends StackTraceElementProxy<?>> {
+
+    private final T originalItem;
+    private final RetracedClass retracedClass;
+    private final RetracedMethod retracedMethod;
+    private final String sourceFile;
+    private final int lineNumber;
+    private final boolean isAmbiguous;
+
+    private RetraceStackTraceProxy(
+        T originalItem,
+        RetracedClass retracedClass,
+        RetracedMethod retracedMethod,
+        String sourceFile,
+        int lineNumber,
+        boolean isAmbiguous) {
+      assert originalItem != null;
+      this.originalItem = originalItem;
+      this.retracedClass = retracedClass;
+      this.retracedMethod = retracedMethod;
+      this.sourceFile = sourceFile;
+      this.lineNumber = lineNumber;
+      this.isAmbiguous = isAmbiguous;
+    }
+
+    public boolean isAmbiguous() {
+      return isAmbiguous;
+    }
+
+    public boolean hasRetracedClass() {
+      return retracedClass != null;
+    }
+
+    public boolean hasRetracedMethod() {
+      return retracedMethod != null;
+    }
+
+    public boolean hasSourceFile() {
+      return sourceFile != null;
+    }
+
+    public boolean hasLineNumber() {
+      return lineNumber != -1;
+    }
+
+    public T getOriginalItem() {
+      return originalItem;
+    }
+
+    public RetracedClass getRetracedClass() {
+      return retracedClass;
+    }
+
+    public RetracedMethod getRetracedMethod() {
+      return retracedMethod;
+    }
+
+    public String getSourceFile() {
+      return sourceFile;
+    }
+
+    private static <T extends StackTraceElementProxy<?>> Builder<T> builder(T originalElement) {
+      return new Builder<>(originalElement);
+    }
+
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    private static class Builder<T extends StackTraceElementProxy<?>> {
+
+      private final T originalElement;
+      private RetracedClass classContext;
+      private RetracedMethod methodContext;
+      private String sourceFile;
+      private int lineNumber = -1;
+      private boolean isAmbiguous;
+
+      private Builder(T originalElement) {
+        this.originalElement = originalElement;
+      }
+
+      private Builder<T> setRetracedClassElement(RetracedClass retracedClass) {
+        this.classContext = retracedClass;
+        return this;
+      }
+
+      private Builder<T> setRetracedMethodElement(RetracedMethod methodElement) {
+        this.methodContext = methodElement;
+        return this;
+      }
+
+      private Builder<T> setSourceFile(String sourceFile) {
+        this.sourceFile = sourceFile;
+        return this;
+      }
+
+      private Builder<T> setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
+        return this;
+      }
+
+      private Builder<T> setAmbiguous(boolean ambiguous) {
+        this.isAmbiguous = ambiguous;
+        return this;
+      }
+
+      private RetraceStackTraceProxy<T> build() {
+        RetracedClass retracedClass = classContext;
+        if (methodContext != null) {
+          retracedClass = methodContext.getHolderClass();
+        }
+        return new RetraceStackTraceProxy<>(
+            originalElement,
+            retracedClass,
+            methodContext,
+            sourceFile != null ? sourceFile : null,
+            lineNumber,
+            isAmbiguous);
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementStringProxy.java
new file mode 100644
index 0000000..dc21cbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementStringProxy.java
@@ -0,0 +1,228 @@
+// 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;
+
+import static com.android.tools.r8.retrace.PlainStackTraceVisitor.firstNonWhiteSpaceCharacterFromIndex;
+import static com.android.tools.r8.retrace.StackTraceElementStringProxy.StringIndex.noIndex;
+
+import com.android.tools.r8.retrace.StackTraceElementProxyRetracer.RetraceStackTraceProxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public final class StackTraceElementStringProxy extends StackTraceElementProxy<String> {
+
+  private final String line;
+  private final List<StringIndex> orderedIndices;
+  private final StringIndex className;
+  private final StringIndex methodName;
+  private final StringIndex sourceFile;
+  private final StringIndex lineNumber;
+
+  private StackTraceElementStringProxy(
+      String line,
+      List<StringIndex> orderedIndices,
+      StringIndex className,
+      StringIndex methodName,
+      StringIndex sourceFile,
+      StringIndex lineNumber) {
+    this.line = line;
+    this.orderedIndices = orderedIndices;
+    this.className = className;
+    this.methodName = methodName;
+    this.sourceFile = sourceFile;
+    this.lineNumber = lineNumber;
+  }
+
+  static StackTraceElementStringProxyBuilder builder(String line) {
+    return new StackTraceElementStringProxyBuilder(line);
+  }
+
+  @Override
+  public boolean hasClassName() {
+    return className.hasIndex();
+  }
+
+  @Override
+  public boolean hasMethodName() {
+    return methodName.hasIndex();
+  }
+
+  @Override
+  public boolean hasFileName() {
+    return sourceFile.hasIndex();
+  }
+
+  @Override
+  public boolean hasLineNumber() {
+    return lineNumber.hasIndex();
+  }
+
+  @Override
+  public String className() {
+    return hasClassName() ? getEntryInLine(className) : null;
+  }
+
+  @Override
+  public String methodName() {
+    return hasMethodName() ? getEntryInLine(methodName) : null;
+  }
+
+  @Override
+  public String fileName() {
+    return hasFileName() ? getEntryInLine(sourceFile) : null;
+  }
+
+  @Override
+  public int lineNumber() {
+    if (!hasLineNumber()) {
+      return -1;
+    }
+    try {
+      return Integer.parseInt(getEntryInLine(lineNumber));
+    } catch (NumberFormatException nfe) {
+      return -1;
+    }
+  }
+
+  public String toRetracedItem(
+      RetraceStackTraceProxy<StackTraceElementStringProxy> retracedProxy, boolean printAmbiguous) {
+    StringBuilder sb = new StringBuilder();
+    int lastSeenIndex = 0;
+    if (retracedProxy.isAmbiguous() && printAmbiguous) {
+      lastSeenIndex = firstNonWhiteSpaceCharacterFromIndex(line, 0);
+      sb.append(line, 0, lastSeenIndex);
+      sb.append("<OR> ");
+    }
+    for (StringIndex index : orderedIndices) {
+      sb.append(line, lastSeenIndex, index.startIndex);
+      sb.append(index.retracedString.apply(retracedProxy, this));
+      lastSeenIndex = index.endIndex;
+    }
+    sb.append(line, lastSeenIndex, line.length());
+    return sb.toString();
+  }
+
+  public String lineNumberAsString() {
+    return getEntryInLine(lineNumber);
+  }
+
+  private String getEntryInLine(StringIndex index) {
+    assert index != noIndex();
+    return line.substring(index.startIndex, index.endIndex);
+  }
+
+  public static class StackTraceElementStringProxyBuilder {
+
+    private final String line;
+    private final List<StringIndex> orderedIndices = new ArrayList<>();
+    private StringIndex className = noIndex();
+    private StringIndex methodName = noIndex();
+    private StringIndex sourceFile = noIndex();
+    private StringIndex lineNumber = noIndex();
+    private int lastSeenStartIndex = -1;
+
+    private StackTraceElementStringProxyBuilder(String line) {
+      this.line = line;
+    }
+
+    public StackTraceElementStringProxyBuilder registerClassName(int startIndex, int endIndex) {
+      ensureLineIndexIncreases(startIndex);
+      className =
+          new StringIndex(
+              startIndex,
+              endIndex,
+              (retraced, original) -> {
+                assert retraced.hasRetracedClass();
+                return retraced.getRetracedClass().getTypeName();
+              });
+      orderedIndices.add(className);
+      return this;
+    }
+
+    public StackTraceElementStringProxyBuilder registerMethodName(int startIndex, int endIndex) {
+      methodName =
+          new StringIndex(
+              startIndex,
+              endIndex,
+              (retraced, original) ->
+                  retraced.hasRetracedMethod()
+                      ? retraced.getRetracedMethod().getMethodName()
+                      : original.methodName());
+      orderedIndices.add(methodName);
+      return this;
+    }
+
+    public StackTraceElementStringProxyBuilder registerSourceFile(int startIndex, int endIndex) {
+      sourceFile =
+          new StringIndex(
+              startIndex,
+              endIndex,
+              (retraced, original) ->
+                  retraced.hasSourceFile() ? retraced.getSourceFile() : original.fileName());
+      orderedIndices.add(sourceFile);
+      return this;
+    }
+
+    public StackTraceElementStringProxyBuilder registerLineNumber(int startIndex, int endIndex) {
+      lineNumber =
+          new StringIndex(
+              startIndex,
+              endIndex,
+              (retraced, original) ->
+                  retraced.hasLineNumber()
+                      ? retraced.getLineNumber() + ""
+                      : original.lineNumberAsString());
+      orderedIndices.add(lineNumber);
+      return this;
+    }
+
+    public StackTraceElementStringProxy build() {
+      return new StackTraceElementStringProxy(
+          line, orderedIndices, className, methodName, sourceFile, lineNumber);
+    }
+
+    private void ensureLineIndexIncreases(int newStartIndex) {
+      if (lastSeenStartIndex >= newStartIndex) {
+        throw new RuntimeException("Parsing has to be incremental in the order of characters.");
+      }
+      lastSeenStartIndex = newStartIndex;
+    }
+  }
+
+  static final class StringIndex {
+
+    private static final StringIndex NO_INDEX = new StringIndex(-1, -1, null);
+
+    static StringIndex noIndex() {
+      return NO_INDEX;
+    }
+
+    private final int startIndex;
+    private final int endIndex;
+    private final BiFunction<
+            RetraceStackTraceProxy<StackTraceElementStringProxy>,
+            StackTraceElementStringProxy,
+            String>
+        retracedString;
+
+    private StringIndex(
+        int startIndex,
+        int endIndex,
+        BiFunction<
+                RetraceStackTraceProxy<StackTraceElementStringProxy>,
+                StackTraceElementStringProxy,
+                String>
+            retracedString) {
+      this.startIndex = startIndex;
+      this.endIndex = endIndex;
+      this.retracedString = retracedString;
+    }
+
+    boolean hasIndex() {
+      return this != NO_INDEX;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java b/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.java
new file mode 100644
index 0000000..fd9c822
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceVisitor.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.retrace;
+
+import java.util.function.Consumer;
+
+public interface StackTraceVisitor<T extends StackTraceElementProxy<?>> {
+
+  void forEach(Consumer<T> consumer);
+}
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 3b68071..d5bc317 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexCallSite;
@@ -568,29 +569,30 @@
     return definition;
   }
 
-  private int largestInputCfVersion = -1;
+  private CfVersion largestInputCfVersion = null;
 
   public boolean canUseConstClassInstructions(InternalOptions options) {
     if (!options.isGeneratingClassFiles()) {
       return true;
     }
-    if (largestInputCfVersion == -1) {
+    if (largestInputCfVersion == null) {
       computeLargestCfVersion();
     }
     return options.canUseConstClassInstructions(largestInputCfVersion);
   }
 
   private synchronized void computeLargestCfVersion() {
-    if (largestInputCfVersion != -1) {
+    if (largestInputCfVersion != null) {
       return;
     }
     for (DexProgramClass clazz : classes()) {
       // Skip synthetic classes which may not have a specified version.
       if (clazz.hasClassFileVersion()) {
-        largestInputCfVersion = Math.max(largestInputCfVersion, clazz.getInitialClassFileVersion());
+        largestInputCfVersion =
+            CfVersion.maxAllowNull(largestInputCfVersion, clazz.getInitialClassFileVersion());
       }
     }
-    assert largestInputCfVersion != -1;
+    assert largestInputCfVersion != null;
   }
 
   public boolean isLiveProgramClass(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java b/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java
deleted file mode 100644
index 6a2151b..0000000
--- a/src/main/java/com/android/tools/r8/shaking/ClassMergingEnqueuerExtension.java
+++ /dev/null
@@ -1,68 +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.shaking;
-
-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.ProgramMethod;
-import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
-import com.google.common.collect.Sets;
-import java.util.Set;
-
-public class ClassMergingEnqueuerExtension
-    implements EnqueuerInstanceOfAnalysis,
-        EnqueuerCheckCastAnalysis,
-        EnqueuerExceptionGuardAnalysis {
-
-  private final Set<DexType> instanceOfTypes = Sets.newIdentityHashSet();
-  private final Set<DexType> checkCastTypes = Sets.newIdentityHashSet();
-  private final Set<DexType> exceptionGuardTypes = Sets.newIdentityHashSet();
-  private final DexItemFactory factory;
-
-  public ClassMergingEnqueuerExtension(DexItemFactory factory) {
-    this.factory = factory;
-  }
-
-  @Override
-  public void traceCheckCast(DexType type, ProgramMethod context) {
-    checkCastTypes.add(type.toBaseType(factory));
-  }
-
-  @Override
-  public void traceInstanceOf(DexType type, ProgramMethod context) {
-    instanceOfTypes.add(type.toBaseType(factory));
-  }
-
-  @Override
-  public void traceExceptionGuard(DexType guard, ProgramMethod context) {
-    exceptionGuardTypes.add(guard);
-  }
-
-  public boolean isCheckCastType(DexProgramClass clazz) {
-    return checkCastTypes.contains(clazz.type);
-  }
-
-  public boolean isInstanceOfType(DexProgramClass clazz) {
-    return instanceOfTypes.contains(clazz.type);
-  }
-
-  public boolean isExceptionGuardType(DexProgramClass clazz) {
-    return exceptionGuardTypes.contains(clazz.type);
-  }
-
-  public boolean isRuntimeCheckType(DexProgramClass clazz) {
-    return isInstanceOfType(clazz) || isCheckCastType(clazz) || isExceptionGuardType(clazz);
-  }
-
-  public void attach(Enqueuer enqueuer) {
-    enqueuer
-        .registerInstanceOfAnalysis(this)
-        .registerCheckCastAnalysis(this)
-        .registerExceptionGuardAnalysis(this);
-  }
-}
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 ec4e675..a7fdc85 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.GenericSignatureEnqueuerAnalysis;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
@@ -78,6 +79,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -340,11 +342,13 @@
   private final GraphReporter graphReporter;
 
   private final LambdaRewriter lambdaRewriter;
+  private final BackportedMethodRewriter backportRewriter;
   private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
   private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
       new IdentityHashMap<>();
   private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
       new IdentityHashMap<>();
+  private final Map<DexMethod, ProgramMethod> methodsWithBackports = new IdentityHashMap<>();
   private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
 
   Enqueuer(
@@ -383,6 +387,8 @@
     liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
     liveFields = new LiveFieldsSet(graphReporter::registerField);
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+    backportRewriter =
+        options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
 
     objectAllocationInfoCollection =
         ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
@@ -1077,6 +1083,10 @@
 
   private void traceInvokeDirect(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+    if (registerBackportInvoke(invokedMethod, context)) {
+      return;
+    }
+
     if (!registerMethodWithTargetAndContext(
         methodAccessInfoCollection::registerInvokeDirectInContext, invokedMethod, context)) {
       return;
@@ -1098,6 +1108,10 @@
 
   private void traceInvokeInterface(
       DexMethod method, ProgramMethod context, KeepReason keepReason) {
+    if (registerBackportInvoke(method, context)) {
+      return;
+    }
+
     if (!registerMethodWithTargetAndContext(
         methodAccessInfoCollection::registerInvokeInterfaceInContext, method, context)) {
       return;
@@ -1117,8 +1131,20 @@
     traceInvokeStatic(invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
   }
 
+  private boolean registerBackportInvoke(DexMethod invokedMethod, ProgramMethod context) {
+    if (backportRewriter != null && backportRewriter.needsDesugaring(invokedMethod)) {
+      methodsWithBackports.putIfAbsent(context.getReference(), context);
+      return true;
+    }
+    return false;
+  }
+
   private void traceInvokeStatic(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+    if (registerBackportInvoke(invokedMethod, context)) {
+      return;
+    }
+
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
         || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -1150,6 +1176,9 @@
   }
 
   void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
+    if (registerBackportInvoke(invokedMethod, context)) {
+      return;
+    }
     // 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);
@@ -1174,6 +1203,10 @@
 
   private void traceInvokeVirtual(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
+    if (registerBackportInvoke(invokedMethod, context)) {
+      return;
+    }
+
     if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
         || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
       pendingReflectiveUses.add(context);
@@ -2698,6 +2731,10 @@
           new KotlinMetadataEnqueuerExtension(
               appView, enqueuerDefinitionSupplier, initialPrunedTypes));
     }
+    if (appView.options().getProguardConfiguration() != null
+        && appView.options().getProguardConfiguration().getKeepAttributes().signature) {
+      registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
+    }
     if (mode.isInitialTreeShaking()) {
       // This is simulating the effect of the "root set" applied rules.
       // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
@@ -2892,6 +2929,7 @@
     synthesizeInterfaceMethodBridges(additions);
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
+    synthesizeBackports(additions);
     if (additions.isEmpty()) {
       return;
     }
@@ -2923,6 +2961,12 @@
     syntheticInterfaceMethodBridges.clear();
   }
 
+  private void synthesizeBackports(SyntheticAdditions additions) {
+    for (ProgramMethod method : methodsWithBackports.values()) {
+      backportRewriter.desugar(method, appInfo, additions::addLiveMethod);
+    }
+  }
+
   private void synthesizeLambdas(SyntheticAdditions additions) {
     if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
       assert lambdaCallSites.isEmpty();
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index 624ee95..765fe31 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -13,4 +13,8 @@
   boolean isAccessModificationEnabled();
 
   boolean isRepackagingEnabled();
+
+  boolean isForceProguardCompatibilityEnabled();
+
+  boolean isKeepAttributesSignatureEnabled();
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 4cb677f..7d75f05 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -82,6 +82,12 @@
     return allowAccessModification;
   }
 
+  public boolean isAllowSignatureAttributeRemovalAllowed(
+      GlobalKeepInfoConfiguration configuration) {
+    return !configuration.isKeepAttributesSignatureEnabled()
+        || !(isPinned() || configuration.isForceProguardCompatibilityEnabled());
+  }
+
   public abstract boolean isTop();
 
   public abstract boolean isBottom();
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
new file mode 100644
index 0000000..ddfea43
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -0,0 +1,95 @@
+// 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.shaking;
+
+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.ProgramMethod;
+import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class RuntimeTypeCheckInfo {
+
+  private final Set<DexType> instanceOfTypes;
+  private final Set<DexType> checkCastTypes;
+  private final Set<DexType> exceptionGuardTypes;
+
+  public RuntimeTypeCheckInfo(
+      Set<DexType> instanceOfTypes, Set<DexType> checkCastTypes, Set<DexType> exceptionGuardTypes) {
+    this.instanceOfTypes = instanceOfTypes;
+    this.checkCastTypes = checkCastTypes;
+    this.exceptionGuardTypes = exceptionGuardTypes;
+  }
+
+  public static class Builder
+      implements EnqueuerInstanceOfAnalysis,
+          EnqueuerCheckCastAnalysis,
+          EnqueuerExceptionGuardAnalysis {
+    private final DexItemFactory factory;
+
+    private final Set<DexType> instanceOfTypes = Sets.newIdentityHashSet();
+    private final Set<DexType> checkCastTypes = Sets.newIdentityHashSet();
+    private final Set<DexType> exceptionGuardTypes = Sets.newIdentityHashSet();
+
+    public Builder(DexItemFactory factory) {
+      this.factory = factory;
+    }
+
+    public RuntimeTypeCheckInfo build() {
+      return new RuntimeTypeCheckInfo(instanceOfTypes, checkCastTypes, exceptionGuardTypes);
+    }
+
+    @Override
+    public void traceCheckCast(DexType type, ProgramMethod context) {
+      checkCastTypes.add(type.toBaseType(factory));
+    }
+
+    @Override
+    public void traceInstanceOf(DexType type, ProgramMethod context) {
+      instanceOfTypes.add(type.toBaseType(factory));
+    }
+
+    @Override
+    public void traceExceptionGuard(DexType guard, ProgramMethod context) {
+      exceptionGuardTypes.add(guard);
+    }
+
+    public void attach(Enqueuer enqueuer) {
+      enqueuer
+          .registerInstanceOfAnalysis(this)
+          .registerCheckCastAnalysis(this)
+          .registerExceptionGuardAnalysis(this);
+    }
+  }
+
+  public boolean isCheckCastType(DexProgramClass clazz) {
+    return checkCastTypes.contains(clazz.type);
+  }
+
+  public boolean isInstanceOfType(DexProgramClass clazz) {
+    return instanceOfTypes.contains(clazz.type);
+  }
+
+  public boolean isExceptionGuardType(DexProgramClass clazz) {
+    return exceptionGuardTypes.contains(clazz.type);
+  }
+
+  public boolean isRuntimeCheckType(DexProgramClass clazz) {
+    return isInstanceOfType(clazz) || isCheckCastType(clazz) || isExceptionGuardType(clazz);
+  }
+
+  public RuntimeTypeCheckInfo rewriteWithLens(GraphLens.NonIdentityGraphLens graphLens) {
+    return new RuntimeTypeCheckInfo(
+        SetUtils.mapIdentityHashSet(instanceOfTypes, graphLens::lookupType),
+        SetUtils.mapIdentityHashSet(checkCastTypes, graphLens::lookupType),
+        SetUtils.mapIdentityHashSet(exceptionGuardTypes, graphLens::lookupType));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index abd2a99..974fdb1 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -248,6 +248,12 @@
   }
 
   private MergeGroup getMergeGroup(DexProgramClass clazz) {
+    if (appView.getSyntheticItems().isSyntheticClass(clazz)) {
+      // In principle it would be valid to merge synthetic classes into program classes.
+      // Doing so may however increase size as static methods will not be de-duplicated
+      // at synthetic finalization.
+      return MergeGroup.DONT_MERGE;
+    }
     if (appView.appInfo().getNoStaticClassMergingSet().contains(clazz.type)) {
       return MergeGroup.DONT_MERGE;
     }
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 9462690..6100000 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -380,21 +380,26 @@
       //     * Have access to the no-arg constructor of its first non-serializable superclass
       return false;
     }
-    TraversalContinuation result =
-        sourceClass.traverseProgramInstanceInitializers(
-            method -> {
-              AbortReason reason = disallowInlining(method, targetClass);
-              if (reason != null) {
-                // Cannot guarantee that markForceInline() will work.
-                if (Log.ENABLED) {
-                  reason.printLogMessageForClass(sourceClass);
+
+    // If there is a constructor in the target, make sure that all source constructors can be
+    // inlined.
+    if (!Iterables.isEmpty(targetClass.programInstanceInitializers())) {
+      TraversalContinuation result =
+          sourceClass.traverseProgramInstanceInitializers(
+              method -> {
+                AbortReason reason = disallowInlining(method, targetClass);
+                if (reason != null) {
+                  // Cannot guarantee that markForceInline() will work.
+                  if (Log.ENABLED) {
+                    reason.printLogMessageForClass(sourceClass);
+                  }
+                  return TraversalContinuation.BREAK;
                 }
-                return TraversalContinuation.BREAK;
-              }
-              return TraversalContinuation.CONTINUE;
-            });
-    if (result.shouldBreak()) {
-      return false;
+                return TraversalContinuation.CONTINUE;
+              });
+      if (result.shouldBreak()) {
+        return false;
+      }
     }
     if (sourceClass.getEnclosingMethodAttribute() != null
         || !sourceClass.getInnerClasses().isEmpty()) {
@@ -1263,7 +1268,7 @@
               ParameterAnnotationsList.empty(),
               code,
               true,
-              method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
+              method.hasClassFileVersion() ? method.getClassFileVersion() : null);
       bridge.setLibraryMethodOverride(method.isLibraryMethodOverride());
       if (method.accessFlags.isPromotedToPublic()) {
         // The bridge is now the public method serving the role of the original method, and should
@@ -1688,7 +1693,7 @@
                 method,
                 appView,
                 new SingleTypeMapperGraphLens(method.getHolderType(), context),
-                context);
+                context.programInstanceInitializers().iterator().next());
         if (constraint == ConstraintWithTarget.NEVER) {
           return AbortReason.UNSAFE_INLINING;
         }
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 2848d3f..a95cfb4 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 MethodLookupResult get(RewrittenPrototypeDescription prototypeChanges);
+    MethodLookupResult get(RewrittenPrototypeDescription prototypeChanges);
   }
 
   private final AppView<?> appView;
@@ -133,7 +133,7 @@
 
   @Override
   protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
+    return mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
   }
 
   @Override
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 93d1248..3625fbb 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
+
+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;
@@ -91,6 +95,33 @@
     return synthesizingContextType;
   }
 
+  void registerPrefixRewriting(DexType hygienicType, AppView<?> appView) {
+    assert hygienicType.toSourceString().startsWith(synthesizingContextType.toSourceString());
+    if (!appView.options().isDesugaredLibraryCompilation()) {
+      return;
+    }
+    DexType rewrittenContext =
+        appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getEmulateLibraryInterface()
+            .get(synthesizingContextType);
+    if (rewrittenContext == null) {
+      return;
+    }
+    String contextPrefix =
+        getBinaryNameFromDescriptor(synthesizingContextType.toDescriptorString());
+    String rewrittenPrefix = getBinaryNameFromDescriptor(rewrittenContext.toDescriptorString());
+    String suffix =
+        getBinaryNameFromDescriptor(hygienicType.toDescriptorString())
+            .substring(contextPrefix.length());
+    DexType rewrittenType =
+        appView
+            .dexItemFactory()
+            .createType(getDescriptorFromClassBinaryName(rewrittenPrefix + suffix));
+    appView.rewritePrefix.rewriteType(hygienicType, rewrittenType);
+  }
+
   void addIfDerivedFromMainDexClass(
       DexProgramClass externalSyntheticClass,
       MainDexClasses mainDexClasses,
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 9dd8bee..2872d1c 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -111,12 +111,11 @@
     List<DexProgramClass> finalSyntheticClasses = new ArrayList<>();
     Set<DexType> derivedMainDexTypesToIgnore = Sets.newIdentityHashSet();
     buildLensAndProgram(
-        application,
+        appView,
         equivalences,
         syntheticItems::containsKey,
         mainDexClasses,
         lensBuilder,
-        options,
         newProgramClasses,
         finalSyntheticClasses,
         derivedMainDexTypesToIgnore);
@@ -241,19 +240,19 @@
   }
 
   private static void buildLensAndProgram(
-      DexApplication app,
+      AppView<?> appView,
       Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
       Predicate<DexType> isSyntheticType,
       MainDexClasses mainDexClasses,
       Builder lensBuilder,
-      InternalOptions options,
       List<DexProgramClass> normalClasses,
       List<DexProgramClass> newSyntheticClasses,
       Set<DexType> derivedMainDexTypesToIgnore) {
-    DexItemFactory factory = options.itemFactory;
+    DexItemFactory factory = appView.dexItemFactory();
 
-    for (DexProgramClass clazz : app.classes()) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (!isSyntheticType.test(clazz.type)) {
+        assert SyntheticItems.verifyNotInternalSynthetic(clazz.type);
         normalClasses.add(clazz);
       }
     }
@@ -264,10 +263,11 @@
         mainDexType -> {
           derivedMainDexTypes.add(mainDexType);
           DexProgramClass mainDexClass =
-              DexProgramClass.asProgramClassOrNull(app.definitionFor(mainDexType));
+              DexProgramClass.asProgramClassOrNull(
+                  appView.appInfo().definitionForWithoutExistenceAssert(mainDexType));
           if (mainDexClass != null) {
             derivedMainDexTypes.addAll(
-                DexAnnotation.readAnnotationSynthesizedClassMap(mainDexClass, options.itemFactory));
+                DexAnnotation.readAnnotationSynthesizedClassMap(mainDexClass, factory));
           }
         });
 
@@ -275,6 +275,7 @@
         (syntheticType, syntheticGroup) -> {
           SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
           SynthesizingContext context = representative.getContext();
+          context.registerPrefixRewriting(syntheticType, appView);
           SyntheticClassBuilder builder =
               new SyntheticClassBuilder(syntheticType, context, factory);
           // TODO(b/158159959): Support grouping multiple methods per synthetic class.
@@ -287,7 +288,7 @@
                     .setCode(m -> definition.getCode());
               });
           DexProgramClass externalSyntheticClass = builder.build();
-          if (shouldAnnotateSynthetics(options)) {
+          if (shouldAnnotateSynthetics(appView.options())) {
             externalSyntheticClass.setAnnotations(
                 externalSyntheticClass
                     .annotations()
@@ -313,7 +314,10 @@
             // TODO(b/168584485): Remove this once class-mapping support is removed.
             DexProgramClass from =
                 DexProgramClass.asProgramClassOrNull(
-                    app.definitionFor(member.getContext().getSynthesizingContextType()));
+                    appView
+                        .appInfo()
+                        .definitionForWithoutExistenceAssert(
+                            member.getContext().getSynthesizingContextType()));
             if (from != null) {
               externalSyntheticClass.addSynthesizedFrom(from);
             }
@@ -418,6 +422,10 @@
       SyntheticMethodDefinition method = (SyntheticMethodDefinition) definition;
       if (SyntheticMethodBuilder.isValidSyntheticMethod(method.getMethod().getDefinition())) {
         methods.add(method);
+      } else {
+        // Failing this check indicates that an optimization has modified the synthetic in a
+        // disruptive way.
+        assert false;
       }
     }
     return methods;
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 6f5c49d..c6726e1 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -308,7 +308,9 @@
     ImmutableMap.Builder<DexType, SyntheticReference> rewrittenItems = ImmutableMap.builder();
     for (SyntheticReference reference : nonLecacySyntheticItems.values()) {
       SyntheticReference rewritten = reference.rewrite(lens);
-      rewrittenItems.put(rewritten.getHolder(), rewritten);
+      if (rewritten != null) {
+        rewrittenItems.put(rewritten.getHolder(), rewritten);
+      }
     }
     // No pending item should need rewriting.
     assert legacyPendingClasses.keySet().equals(lens.rewriteTypes(legacyPendingClasses.keySet()));
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 86704ea..28913d0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -24,6 +25,11 @@
   }
 
   @Override
+  DexReference getReference() {
+    return method;
+  }
+
+  @Override
   DexType getHolder() {
     return method.holder;
   }
@@ -35,14 +41,23 @@
       return null;
     }
     assert clazz.isProgramClass();
-    ProgramMethod definition = clazz.asProgramClass().lookupProgramMethod(this.method);
-    return new SyntheticMethodDefinition(getContext(), definition);
+    ProgramMethod definition = clazz.asProgramClass().lookupProgramMethod(method);
+    return definition != null ? new SyntheticMethodDefinition(getContext(), definition) : null;
   }
 
   @Override
   SyntheticReference rewrite(NonIdentityGraphLens lens) {
-    SynthesizingContext context = getContext().rewrite(lens);
     DexMethod rewritten = lens.lookupMethod(method);
+    // If the reference has been non-trivially rewritten the compiler has changed it and it can no
+    // longer be considered a synthetic. The context may or may not have changed.
+    if (method != rewritten && !lens.isSimpleRenaming(method.holder, rewritten.holder)) {
+      // If the referenced item is rewritten, it should be moved to another holder as the
+      // synthetic holder is no longer part of the synthetic collection.
+      assert method.holder != rewritten.holder;
+      assert SyntheticItems.verifyNotInternalSynthetic(rewritten.holder);
+      return null;
+    }
+    SynthesizingContext context = getContext().rewrite(lens);
     return context == getContext() && rewritten == method
         ? this
         : new SyntheticMethodReference(context, rewritten);
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 408324e..6618378 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.synthesis;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import java.util.function.Function;
@@ -22,6 +23,8 @@
 
   abstract SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions);
 
+  abstract DexReference getReference();
+
   final SynthesizingContext getContext() {
     return context;
   }
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 8d66524..7259af1 100644
--- a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -7,9 +7,18 @@
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 public class ConsumerUtils {
 
+  public static <S, T> Function<S, Consumer<T>> curry(BiConsumer<S, T> function) {
+    return arg -> arg2 -> function.accept(arg, arg2);
+  }
+
+  public static <S, T> Consumer<T> apply(BiConsumer<S, T> function, S arg) {
+    return curry(function).apply(arg);
+  }
+
   public static <T> Consumer<T> acceptIfNotSeen(Consumer<T> consumer, Set<T> seen) {
     return element -> {
       if (seen.add(element)) {
diff --git a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
index 64e8537..b7c7a87 100644
--- a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
@@ -4,11 +4,20 @@
 
 package com.android.tools.r8.utils;
 
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
 public class FunctionUtils {
 
+  public static <S, T, R> Function<S, Function<T, R>> curry(BiFunction<S, T, R> function) {
+    return arg -> arg2 -> function.apply(arg, arg2);
+  }
+
+  public static <S, T, R> Function<T, R> apply(BiFunction<S, T, R> function, S arg) {
+    return curry(function).apply(arg);
+  }
+
   public static <T, R> void forEachApply(
       Iterable<T> list, Function<T, Consumer<R>> func, R argument) {
     for (T t : list) {
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 8bc1a55..c1fe00e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -15,7 +15,9 @@
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dex.Marker.Backend;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
@@ -103,7 +105,7 @@
     ON
   }
 
-  public static final int SUPPORTED_CF_MAJOR_VERSION = Opcodes.V11;
+  public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V11;
   public static final int SUPPORTED_DEX_VERSION =
       AndroidApiLevel.LATEST.getDexVersion().getIntValue();
 
@@ -180,7 +182,8 @@
     protoShrinking.enableGeneratedMessageLiteShrinking = true;
     protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
     protoShrinking.enableGeneratedExtensionRegistryShrinking = true;
-    protoShrinking.enableEnumLiteProtoShrinking = true;
+    // TODO(b/170798502): Reland enum unboxing for proto enums.
+    // protoShrinking.enableEnumLiteProtoShrinking = true;
   }
 
   void disableAllOptimizations() {
@@ -347,6 +350,7 @@
         new Marker(tool)
             .setVersion(Version.LABEL)
             .setCompilationMode(debug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
+            .setBackend(isGeneratingClassFiles() ? Backend.CF : Backend.DEX)
             .setHasChecksums(encodeChecksums);
     // Compiling with D8 and L8 is always with a min API level and desugaring to that level. If
     // desugaring is explicitly turned off for D8 the input is expected to already have been
@@ -546,6 +550,16 @@
         && (isShrinking() || isMinifying());
   }
 
+  @Override
+  public boolean isForceProguardCompatibilityEnabled() {
+    return forceProguardCompatibility;
+  }
+
+  @Override
+  public boolean isKeepAttributesSignatureEnabled() {
+    return proguardConfiguration.getKeepAttributes().signature;
+  }
+
   /**
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
@@ -702,10 +716,10 @@
 
   private static class TypeVersionPair {
 
-    final int version;
+    final CfVersion version;
     final DexType type;
 
-    public TypeVersionPair(int version, DexType type) {
+    public TypeVersionPair(CfVersion version, DexType type) {
       this.version = version;
       this.type = type;
     }
@@ -951,7 +965,7 @@
     }
   }
 
-  public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {
+  public void warningMissingEnclosingMember(DexType clazz, Origin origin, CfVersion version) {
     TypeVersionPair pair = new TypeVersionPair(version, clazz);
     synchronized (missingEnclosingMembers) {
       missingEnclosingMembers.computeIfAbsent(origin, k -> new ArrayList<>()).add(pair);
@@ -1065,7 +1079,7 @@
             builder.append(", ");
           }
           builder.append(pair.type);
-          printOutdatedToolchain |= pair.version < 49;
+          printOutdatedToolchain |= pair.version.isLessThan(CfVersion.V1_5);
         }
         reporter.info(new StringDiagnostic(builder.toString(), origin));
       }
@@ -1387,14 +1401,14 @@
     return result;
   }
 
-  public boolean canUseConstClassInstructions(int cfVersion) {
+  public boolean canUseConstClassInstructions(CfVersion cfVersion) {
     assert isGeneratingClassFiles();
-    return cfVersion >= requiredCfVersionForConstClassInstructions();
+    return cfVersion.isGreaterThanOrEqual(requiredCfVersionForConstClassInstructions());
   }
 
-  public int requiredCfVersionForConstClassInstructions() {
+  public CfVersion requiredCfVersionForConstClassInstructions() {
     assert isGeneratingClassFiles();
-    return Opcodes.V1_5;
+    return CfVersion.V1_5;
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 8297a9f..f4115a4 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -60,10 +60,10 @@
     return build(appView, null);
   }
 
-  public T build(AppView<AppInfoWithLiveness> appView, GraphLens applied) {
+  public T build(AppView<AppInfoWithLiveness> appView, GraphLens appliedGraphLens) {
     T result = factory.apply(methods.size());
     for (DexMethod oldMethod : methods) {
-      DexMethod method = appView.graphLens().getRenamedMethodSignature(oldMethod, applied);
+      DexMethod method = appView.graphLens().getRenamedMethodSignature(oldMethod, appliedGraphLens);
       DexProgramClass holder = appView.definitionForHolder(method).asProgramClass();
       result.createAndAdd(holder, holder.lookupMethod(method));
     }
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 008ff1f..7cbcf63 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -9,6 +9,8 @@
 
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.nio.file.Paths;
 import java.util.Collection;
@@ -120,4 +122,24 @@
             .build());
     assertTrue(testExecuted[0]);
   }
+
+  @Test
+  public void backendValueCompatibilityTestForMinApi() {
+    DexItemFactory factory = new DexItemFactory();
+    DexString markerString =
+        factory.createString(
+            "~~D8{\"compilation-mode\":\"debug\",\"has-checksums\":false,"
+                + "\"min-api\":21,\"sha-1\":\"engineering\",\"version\":\"master\" }");
+    assertEquals("dex", Marker.parse(markerString).getBackend());
+  }
+
+  @Test
+  public void backendValueCompatibilityTestForR8Cf() {
+    DexItemFactory factory = new DexItemFactory();
+    DexString markerString =
+        factory.createString(
+            "~~R8{\"compilation-mode\":\"release\",\"has-checksums\":true,"
+                + "\"sha-1\":\"engineering\",\"version\":\"master\" }");
+    assertEquals("cf", Marker.parse(markerString).getBackend());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java b/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
index 6ea338c..c71ca8b 100644
--- a/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
+++ b/src/test/java/com/android/tools/r8/FailCompilationOnFutureVersionsTest.java
@@ -21,7 +21,7 @@
 @RunWith(Parameterized.class)
 public class FailCompilationOnFutureVersionsTest extends TestBase {
 
-  static final int UNSUPPORTED_CF_VERSION = InternalOptions.SUPPORTED_CF_MAJOR_VERSION + 1;
+  static final int UNSUPPORTED_CF_VERSION = InternalOptions.SUPPORTED_CF_VERSION.major() + 1;
   static final int UNSUPPORTED_DEX_VERSION = InternalOptions.SUPPORTED_DEX_VERSION + 1;
 
   private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/MarkerMatcher.java b/src/test/java/com/android/tools/r8/MarkerMatcher.java
index ea6ea4a..d766770 100644
--- a/src/test/java/com/android/tools/r8/MarkerMatcher.java
+++ b/src/test/java/com/android/tools/r8/MarkerMatcher.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -108,6 +109,20 @@
     };
   }
 
+  public static Matcher<Marker> markerBackend(Backend backend) {
+    return new MarkerMatcher() {
+      @Override
+      protected boolean eval(Marker marker) {
+        return marker.getBackend().equals(backend.name().toLowerCase());
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText(Marker.BACKEND + " ").appendText(backend.name().toLowerCase());
+      }
+    };
+  }
+
   public static Matcher<Marker> markerIsDesugared() {
     return new MarkerMatcher() {
       @Override
diff --git a/src/test/java/com/android/tools/r8/MarkersTest.java b/src/test/java/com/android/tools/r8/MarkersTest.java
index 650cf39..1fdc6ae 100644
--- a/src/test/java/com/android/tools/r8/MarkersTest.java
+++ b/src/test/java/com/android/tools/r8/MarkersTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import static com.android.tools.r8.MarkerMatcher.assertMarkersMatch;
+import static com.android.tools.r8.MarkerMatcher.markerBackend;
 import static com.android.tools.r8.MarkerMatcher.markerCompilationMode;
 import static com.android.tools.r8.MarkerMatcher.markerDesugaredLibraryIdentifier;
 import static com.android.tools.r8.MarkerMatcher.markerHasChecksums;
@@ -118,6 +119,7 @@
         allOf(
             markerTool(Tool.D8),
             markerCompilationMode(compilationMode),
+            markerBackend(Backend.DEX),
             markerIsDesugared(),
             markerMinApi(apiLevel),
             not(markerHasDesugaredLibraryIdentifier()));
@@ -144,6 +146,7 @@
         allOf(
             markerTool(Tool.D8),
             markerCompilationMode(compilationMode),
+            markerBackend(Backend.CF),
             markerIsDesugared(),
             markerMinApi(apiLevel),
             not(markerHasDesugaredLibraryIdentifier()));
@@ -171,6 +174,7 @@
         allOf(
             markerTool(Tool.R8),
             markerCompilationMode(compilationMode),
+            markerBackend(Backend.DEX),
             markerIsDesugared(),
             markerMinApi(apiLevel),
             not(markerHasDesugaredLibraryIdentifier()));
@@ -196,6 +200,7 @@
         allOf(
             markerTool(Tool.R8),
             markerCompilationMode(compilationMode),
+            markerBackend(Backend.CF),
             not(markerIsDesugared()),
             not(markerHasMinApi()),
             not(markerHasDesugaredLibraryIdentifier()));
@@ -242,6 +247,7 @@
         allOf(
             markerTool(Tool.R8),
             markerCompilationMode(compilationMode),
+            markerBackend(Backend.CF),
             not(markerIsDesugared()),
             not(markerHasMinApi()),
             not(markerHasDesugaredLibraryIdentifier())));
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 67721e4..4dc03b1 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1024,6 +1024,8 @@
           .put("506-verify-aput", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Test with invalid register usage: returns a register of either long or double.
           .put("510-checker-try-catch", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with backport method which is still present in DX input.
+          .put("530-checker-lse2", TestCondition.match(TestCondition.tools(DexTool.DX)))
           // Test with invalid register usage: contains an int-to-byte on the result of aget-object.
           .put("518-null-array-get", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Test with invalid register usage: phi of int and float.
@@ -1032,11 +1034,21 @@
           .put("552-checker-primitive-typeprop", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Test with invalid register usage: invoke-static {v0,v0}, foo(IL)V
           .put("557-checker-ref-equivalent", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // Test with smali code that calls a method that needs to be desugared.
+          // The smali code is only present in the non-legacy test distrubution, so this only fails
+          // when running the "default" runtime.
+          .put(
+              "567-checker-compare",
+              TestCondition.or(
+                  TestCondition.match(TestCondition.runtimes(Runtime.ART_DEFAULT)),
+                  TestCondition.match(TestCondition.tools(DexTool.DX))))
           // This test is starting from invalid dex code. It splits up a double value and uses
           // the first register of a double with the second register of another double.
           .put("800-smali", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Contains a loop in the class hierarchy.
           .put("804-class-extends-itself", TestCondition.any())
+          // Test with backport method which is still present in DX input.
+          .put("912-classes", TestCondition.match(TestCondition.tools(DexTool.DX)))
           // These tests have illegal class flag combinations, so we reject them.
           .put("161-final-abstract-class", TestCondition.any())
           .build();
@@ -2398,7 +2410,7 @@
       CompilationMode compilationMode)
       throws Throwable {
     if (specification.expectedToFailWithX8) {
-      expectException(CompilationError.class);
+      expectedException = true;
       try {
         executeCompilerUnderTest(
             compilerUnderTest,
@@ -2406,7 +2418,8 @@
             resultDir.getCanonicalPath(),
             compilationMode,
             new CompilationOptions(specification));
-      } catch (CompilationFailedException e) {
+      } catch (CompilationFailedException | CompilationError e) {
+        expectException(CompilationError.class);
         throw new CompilationError(e.getMessage(), e);
       }
       System.err.println("Should have failed R8/D8 compilation with a CompilationError.");
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 5a90d52..23ec0c9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -17,13 +17,13 @@
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
@@ -172,6 +172,10 @@
   }
 
   private boolean shouldCompileFail() {
+    if (input == Input.DX && getFailingCompileDxToDex().contains(mainClass)) {
+      assert output == Output.DEX;
+      return true;
+    }
     if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
       return true;
     }
@@ -182,7 +186,7 @@
   }
 
   @Test
-  public void outputIsIdentical() throws IOException, InterruptedException, ExecutionException {
+  public void outputIsIdentical() throws IOException {
     if (shouldCompileFail()) {
       // We expected an exception, but got none.
       // Return to ensure that this test fails due to the missing exception.
@@ -261,6 +265,10 @@
 
   protected abstract Set<String> getFailingCompileCfToDex();
 
+  protected Set<String> getFailingCompileDxToDex() {
+    return ImmutableSet.of();
+  }
+
   // TODO(mathiasr): Add CompilerSet for CfToDex so we can fold this into getFailingRun().
   protected abstract Set<String> getFailingRunCfToDex();
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 5eaef9e..04ff03f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -158,6 +158,15 @@
   }
 
   @Override
+  protected Set<String> getFailingCompileDxToDex() {
+    return new ImmutableSet.Builder<String>()
+        .add("regress_72361252.Test") // requires desugar
+        .add("regress_70703087.Test") // requires desugar
+        .add("regress_70737019.Test") // requires desugar
+        .build();
+  }
+
+  @Override
   protected Set<String> getFailingCompileCf() {
     return new ImmutableSet.Builder<String>()
         .build();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 4149659..37f8a22 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -126,6 +126,15 @@
                             dexItemFactory, horizontallyMergedClasses))));
   }
 
+  public T addHorizontallyMergedClassesInspectorIf(
+      boolean condition, Consumer<HorizontallyMergedClassesInspector> inspector) {
+
+    if (condition) {
+      return addHorizontallyMergedClassesInspector(inspector);
+    }
+    return self();
+  }
+
   public T addHorizontallyMergedLambdaClassesInspector(
       Consumer<HorizontallyMergedLambdaClassesInspector> inspector) {
     return addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/cf/CfVersionTest.java b/src/test/java/com/android/tools/r8/cf/CfVersionTest.java
new file mode 100644
index 0000000..48871e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CfVersionTest.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.cf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class CfVersionTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public CfVersionTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void test() throws Exception {
+    CfVersion v1_1 = CfVersion.V1_1;
+    assertEquals(Opcodes.V1_1, v1_1.raw());
+    assertEquals(45, v1_1.major());
+    assertEquals(3, v1_1.minor());
+
+    CfVersion v1_2 = CfVersion.V1_2;
+    assertEquals(Opcodes.V1_2, v1_2.raw());
+    assertEquals(46, v1_2.major());
+    assertEquals(0, v1_2.minor());
+
+    CfVersion v9 = CfVersion.V9;
+    assertEquals(Opcodes.V9, v9.raw());
+    assertEquals(53, v9.major());
+    assertEquals(0, v9.minor());
+
+    assertLessThan(v1_1, v1_2);
+    assertLessThan(v1_2, v9);
+  }
+
+  private static void assertLessThan(CfVersion less, CfVersion more) {
+    assertFalse(less.isEqual(more));
+    assertEquals(-1, less.compareTo(more));
+    assertEquals(1, more.compareTo(less));
+    assertTrue(less.isLessThan(more));
+    assertTrue(less.isLessThanOrEqual(more));
+    assertFalse(less.isGreaterThan(more));
+    assertFalse(less.isGreaterThanOrEqual(more));
+    assertFalse(more.isLessThan(less));
+    assertFalse(more.isLessThanOrEqual(less));
+    assertTrue(more.isGreaterThan(less));
+    assertTrue(more.isGreaterThanOrEqual(less));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
index c25dc40..da83514 100644
--- a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverClassInline;
@@ -18,7 +17,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
 public class GetClassLdcClassTest extends TestBase {
@@ -26,16 +24,16 @@
   static final String EXPECTED = StringUtils.lines(Runner.class.getName());
 
   private final TestParameters parameters;
-  private final int version;
+  private final CfVersion version;
 
   @Parameterized.Parameters(name = "{0}, cf:{1}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimesAndApiLevels().build(),
-        new Integer[] {Opcodes.V1_4, Opcodes.V1_5});
+        new CfVersion[] {CfVersion.V1_1, CfVersion.V1_4, CfVersion.V1_5});
   }
 
-  public GetClassLdcClassTest(TestParameters parameters, int version) {
+  public GetClassLdcClassTest(TestParameters parameters, CfVersion version) {
     this.parameters = parameters;
     this.version = version;
   }
@@ -99,11 +97,11 @@
             inspector -> {
               if (parameters.isCfRuntime()) {
                 // We are assuming the runtimes we are testing are post CF SE 1.4 (version 48).
-                int cfVersionForRuntime = getVersion(inspector, TestClass.class);
-                assertNotEquals(Opcodes.V1_4, cfVersionForRuntime);
+                CfVersion cfVersionForRuntime = getVersion(inspector, TestClass.class);
+                assertTrue(CfVersion.V1_4.isLessThan(cfVersionForRuntime));
                 // Check that the downgraded class has been bumped to at least SE 1.5 (version 49).
-                int cfVersionAfterUpgrade = getVersion(inspector, Runner.class);
-                assertTrue(cfVersionAfterUpgrade >= Opcodes.V1_5);
+                CfVersion cfVersionAfterUpgrade = getVersion(inspector, Runner.class);
+                assertTrue(CfVersion.V1_4.isLessThan(cfVersionAfterUpgrade));
               }
               // Check that the method uses a const class instruction.
               assertTrue(
@@ -115,11 +113,11 @@
             });
   }
 
-  private static int getVersion(CodeInspector inspector, Class<?> clazz) {
+  private static CfVersion getVersion(CodeInspector inspector, Class<?> clazz) {
     return inspector.clazz(clazz).getDexProgramClass().getInitialClassFileVersion();
   }
 
-  private static void checkVersion(CodeInspector inspector, Class<?> clazz, int version) {
+  private static void checkVersion(CodeInspector inspector, Class<?> clazz, CfVersion version) {
     assertEquals(version, getVersion(inspector, clazz));
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 279004e..070d6c8 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -72,13 +72,10 @@
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    assertThrowsWithHorizontalClassMerging(
-        () -> {
-          if (data().stream().count() > 0) {
-            r8R8Debug = compileR8(CompilationMode.DEBUG);
-            r8R8Release = compileR8(CompilationMode.RELEASE);
-          }
-        });
+    if (data().stream().count() > 0) {
+      r8R8Debug = compileR8(CompilationMode.DEBUG);
+      r8R8Release = compileR8(CompilationMode.RELEASE);
+    }
   }
 
   @Parameters(name = "{0}")
@@ -124,7 +121,6 @@
 
   @Test
   public void testRetrace() throws IOException {
-    expectThrowsWithHorizontalClassMerging();
     ProcessResult processResult =
         ToolHelper.runProcess(
             new ProcessBuilder()
@@ -210,13 +206,11 @@
 
   @Test
   public void testSignatures() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     testParseSignaturesInJar(r8R8Release.getFirst());
   }
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     Path helloJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
     ProcessResult runResult = ToolHelper.runJava(helloJar, "hello.Hello");
     assertEquals(0, runResult.exitCode);
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
index 1c7a809..722e4f6 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
@@ -73,7 +73,6 @@
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     // Run hello.jar to ensure it exists and is valid.
     Path hello = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
     ProcessResult runHello = ToolHelper.runJava(hello, "hello.Hello");
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/MajorMinorStackTraceVerificationTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/MajorMinorStackTraceVerificationTest.java
new file mode 100644
index 0000000..44dfe61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/MajorMinorStackTraceVerificationTest.java
@@ -0,0 +1,51 @@
+// 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 com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cf.CfVersion;
+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 MajorMinorStackTraceVerificationTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MajorMinorStackTraceVerificationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(Main.class).setVersion(CfVersion.V1_1).stripFrames("main").transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World");
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        System.out.println("Hello World");
+      } else {
+        System.out.println("World, Hello");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
new file mode 100644
index 0000000..5ee01b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
@@ -0,0 +1,73 @@
+// 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.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class ClassesWithNativeMethodsTest extends HorizontalClassMergingTestBase {
+  public ClassesWithNativeMethodsTest(
+      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())
+        .addHorizontallyMergedClassesInspector(inspector -> inspector.assertNoClassesMerged())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(
+            allOf(
+                containsString("java.lang.UnsatisfiedLinkError:"),
+                containsString("com.android.tools.r8.classmerging.horizontal.b.a(")))
+        .inspectFailure(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(B.class), isPresent());
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+
+    @NeverInline
+    public String foo() {
+      return "foo";
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public B() {
+      System.out.println("b");
+    }
+
+    public native String foo();
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      System.out.println(new A().foo());
+      System.out.println(new B().foo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java
new file mode 100644
index 0000000..b14763f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.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.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+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 org.junit.Test;
+
+public class CompanionClassMergingTest extends HorizontalClassMergingTestBase {
+  public CompanionClassMergingTest(
+      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)
+        .addOptionsModification(options -> options.enableClassInlining = false)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging,
+            inspector -> inspector.assertMergedInto(B.Companion.class, A.Companion.class))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("foo a 0", "foo b 1")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(B.class), isPresent());
+
+              assertThat(codeInspector.clazz(A.Companion.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.Companion.class),
+                  notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  @NeverClassInline
+  public static class A {
+    public static final Companion companion = new Companion(null);
+    public static String foo;
+
+    static {
+      foo = "foo a " + Main.next();
+    }
+
+    public static String access$getFoo$cp() {
+      return foo;
+    }
+
+    public static class Companion {
+      public Companion() {}
+
+      public Companion(Object obj) {
+        this();
+      }
+
+      public String getFoo() {
+        return access$getFoo$cp();
+      }
+    }
+  }
+
+  @NeverClassInline
+  public static class B {
+    public static final Companion companion = new Companion(null);
+    public static String foo;
+
+    static {
+      foo = "foo b " + Main.next();
+    }
+
+    public static String access$getFoo$cp() {
+      return foo;
+    }
+
+    public static class Companion {
+      public Companion() {}
+
+      public Companion(Object obj) {
+        this();
+      }
+
+      public String getFoo() {
+        return access$getFoo$cp();
+      }
+    }
+  }
+
+  public static class Main {
+    static int COUNT = 0;
+
+    public static int next() {
+      return COUNT++;
+    }
+
+    public static void main(String[] args) {
+      System.out.println(A.companion.getFoo());
+      System.out.println(B.companion.getFoo());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
new file mode 100644
index 0000000..2383a03
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
@@ -0,0 +1,80 @@
+// 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.notIf;
+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 org.junit.Test;
+
+public class ConstructorCantInlineTest extends HorizontalClassMergingTestBase {
+  public ConstructorCantInlineTest(
+      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("c", "foo: foo")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(A.class), not(isPresent()));
+              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(codeInspector.clazz(C.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(D.class), notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  public static class A {
+    public String foo() {
+      return "foo";
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NeverClassInline
+  public static class B extends A {}
+
+  @NeverClassInline
+  public static class C {
+    C() {
+      System.out.println("c");
+    }
+  }
+
+  public static class D {
+    D() {
+      foo(new B());
+    }
+
+    @NeverInline
+    static void foo(A a) {
+      System.out.println("foo: " + a.foo());
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new C();
+      new D();
+    }
+  }
+}
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
index dd1b6ca..109b0bc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -34,6 +34,9 @@
         .addKeepMainRule(Main.class)
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertMerged(A.class, B.class).assertMergedIntoDifferentType(B.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
index ffdae33..4cdca16 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
@@ -5,11 +5,13 @@
 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.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.CompanionClassMergingTest.A;
+import com.android.tools.r8.classmerging.horizontal.CompanionClassMergingTest.B;
 import org.junit.Test;
 
 public class EmptyClassTest extends HorizontalClassMergingTestBase {
@@ -26,36 +28,36 @@
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccess()
+        .assertSuccessWithOutputLines("a", "b: foo")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                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());
-              }
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
             });
   }
 
   @NeverClassInline
-  public static class A {}
+  public static class A {
+    public A() {
+      System.out.println("a");
+    }
+  }
 
   @NeverClassInline
   public static class B {
-    // TODO(b/164924717): remove non overlapping constructor requirement
-    public B(String s) {}
+    public B(String s) {
+      System.out.println("b: " + s);
+    }
   }
 
   public static class Main {
     public static void main(String[] args) {
       A a = new A();
-      System.out.println(a);
-      B b = new B("");
-      System.out.println(b);
+      B b = new B("foo");
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritsFromLibraryClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritsFromLibraryClassTest.java
new file mode 100644
index 0000000..cfafb12
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritsFromLibraryClassTest.java
@@ -0,0 +1,90 @@
+// 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.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.SuperConstructorCallsVirtualMethodTest.A;
+import com.android.tools.r8.classmerging.horizontal.SuperConstructorCallsVirtualMethodTest.B;
+import com.android.tools.r8.classmerging.horizontal.SuperConstructorCallsVirtualMethodTest.Main;
+import com.android.tools.r8.classmerging.horizontal.SuperConstructorCallsVirtualMethodTest.Parent;
+import java.util.ArrayList;
+import org.junit.Test;
+
+public class InheritsFromLibraryClassTest extends HorizontalClassMergingTestBase {
+  public InheritsFromLibraryClassTest(
+      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("a", "foo a", "b", "foo")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Parent.class), isPresent());
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(C.class), isPresent());
+            });
+  }
+
+  public static class Parent {
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class A extends Parent {
+    public A() {
+      System.out.println("a");
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println("foo a");
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends Parent {
+    public B() {
+      System.out.println("b");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends ArrayList {
+    public C() {}
+
+    public void fooB(B b) {
+      b.foo();
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A().foo();
+      new C().fooB(new B());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergeNonFinalAndFinalClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergeNonFinalAndFinalClassTest.java
new file mode 100644
index 0000000..a5eb885
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergeNonFinalAndFinalClassTest.java
@@ -0,0 +1,72 @@
+// 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.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class MergeNonFinalAndFinalClassTest extends HorizontalClassMergingTestBase {
+  public MergeNonFinalAndFinalClassTest(
+      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)
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector ->
+                assertThat(
+                    inspector.clazz(A.class), notIf(isFinal(), enableHorizontalClassMerging)))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("a", "b", "b", "c");
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new A();
+      new B();
+      new C();
+    }
+  }
+
+  @NeverClassInline
+  public static final class A {
+    public A() {
+      System.out.println("a");
+    }
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class B {
+    public B() {
+      System.out.println("b");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends B {
+    public C() {
+      System.out.println("c");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java
new file mode 100644
index 0000000..2fa8996
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java
@@ -0,0 +1,83 @@
+// 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.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class MergedSuperMethodIsDefaultMethodTest extends HorizontalClassMergingTestBase {
+  public MergedSuperMethodIsDefaultMethodTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(this.getClass())
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I.foo")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(I.class), isPresent());
+              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
+            });
+  }
+
+  @NoVerticalClassMerging
+  public interface I {
+    @NeverInline
+    default void foo() {
+      System.out.println("I.foo");
+    }
+  }
+
+  public abstract static class A implements I {}
+
+  @NeverClassInline
+  public static class B extends A {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class C extends A {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      callA(args.length == 0 ? new C() : new B());
+    }
+
+    @NeverInline
+    private static void callA(A a) {
+      a.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
index 91dc3b1..e357250 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
@@ -21,19 +21,16 @@
 
   @Test
   public void test() throws Exception {
-    if (enableHorizontalClassMerging) {
-      thrown.expect(Throwable.class);
-    }
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessOnMergedClassTestClasses.class)
         .addKeepMainRule(Main.class)
         .addOptionsModification(
             options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
-        .addHorizontallyMergedLambdaClassesInspector(
+        .addHorizontallyMergedClassesInspector(
             inspector -> {
               if (enableHorizontalClassMerging) {
-                inspector.assertMerged(C.class, D.class);
+                inspector.assertMerged(C.class, D.class).assertMergedIntoDifferentType(D.class);
               }
             })
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
index 3581801..3afcfb2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
@@ -22,9 +22,6 @@
 
   @Test
   public void test() throws Exception {
-    if (enableHorizontalClassMerging) {
-      thrown.expect(Throwable.class);
-    }
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessWithMergedTypeTestClasses.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java
new file mode 100644
index 0000000..9ecbf5a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class VerticallyMergedClassDistinguishedByCheckCastTest
+    extends HorizontalClassMergingTestBase {
+
+  public VerticallyMergedClassDistinguishedByCheckCastTest(
+      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)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("fail", "bar")
+        .inspect(codeInspector -> {});
+  }
+
+  @NeverClassInline
+  public static class Parent {
+    @NeverInline
+    public void bar() {
+      System.out.println("bar");
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends Parent {}
+
+  public static class Main {
+    @NeverInline
+    public static void checkObject(Object o) {
+      try {
+        Parent b = (Parent) o;
+        b.bar();
+      } catch (ClassCastException ex) {
+        System.out.println("fail");
+      }
+    }
+
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      checkObject(a);
+      checkObject(b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java
new file mode 100644
index 0000000..bba42bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class VerticallyMergedClassDistinguishedByInstanceOfTest
+    extends HorizontalClassMergingTestBase {
+
+  public VerticallyMergedClassDistinguishedByInstanceOfTest(
+      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)
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("false", "true")
+        .inspect(codeInspector -> {});
+  }
+
+  @NeverClassInline
+  public static class Parent {
+    @NeverInline
+    public void bar() {
+      System.out.println("bar");
+    }
+  }
+
+  @NeverClassInline
+  public static class A {
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends Parent {}
+
+  public static class Main {
+    @NeverInline
+    public static void checkObject(Object o) {
+      System.out.println(o instanceof Parent);
+    }
+
+    public static void main(String[] args) {
+      A a = new A();
+      B b = new B();
+      checkObject(a);
+      checkObject(b);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
new file mode 100644
index 0000000..982fb65
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -0,0 +1,78 @@
+// 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.vertical;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Arrays;
+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 ForceInlineConstructorWithRetargetedLibMemberTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ForceInlineConstructorWithRetargetedLibMemberTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addInnerClasses(getClass())
+          .addKeepMainRule(TestClass.class)
+          .enableCoreLibraryDesugaring(parameters.getApiLevel())
+          .enableNeverClassInliningAnnotations()
+          .setMinApi(parameters.getApiLevel())
+          .compile();
+      assertTrue(parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    } catch (CompilationFailedException e) {
+      // TODO(b/170677722): Fix compilation failure.
+      assertTrue(parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
+      assertTrue(e.getCause() instanceof InternalCompilerError);
+      assertThat(e.getCause().getMessage(), containsString("FORCE inlining on non-inlinable"));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B(args);
+    }
+  }
+
+  static class A {
+
+    A(String[] args) {
+      Arrays.stream(args);
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+
+    B(String[] args) {
+      super(args);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
index ecf49fc..0f28809 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.classmerging.vertical.NonReboundFieldAccessWithMergedTypeTest.GreetingBase;
 import com.android.tools.r8.classmerging.vertical.testclasses.NonReboundFieldAccessOnMergedClassTestClasses;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,13 +30,12 @@
 
   @Test
   public void test() throws Exception {
-    thrown.expect(Throwable.class);
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessOnMergedClassTestClasses.class)
         .addKeepMainRule(Main.class)
         .addVerticallyMergedClassesInspector(
-            inspector -> inspector.assertMergedIntoSubtype(GreetingBase.class))
+            inspector -> inspector.assertMergedIntoSubtype(C.class))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
index 5a3f831..6d8cef1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessWithMergedTypeTest.java
@@ -30,7 +30,6 @@
 
   @Test
   public void test() throws Exception {
-    thrown.expect(Throwable.class);
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessWithMergedTypeTestClasses.class)
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 13d59dc..44bcc80 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -2108,14 +2108,6 @@
           }
           return false;
         }
-        if (isInLambdaClass(mirror, location)) {
-          // Lambda classes must be skipped since they are only wrappers around lambda code.
-          if (DEBUG_TESTS) {
-            System.out.println("Skipping lambda class wrapper method");
-          }
-          wrapper.enqueueCommandFirst(stepCommand);
-          return true;
-        }
         if (isSyntheticMethod(mirror, location)) {
           if (DEBUG_TESTS) {
             System.out.println("Skipping synthetic method");
@@ -2160,11 +2152,6 @@
         return false;
       }
 
-      private static boolean isInLambdaClass(VmMirror mirror, Location location) {
-        String classSig = mirror.getClassSignature(location.classID);
-        return classSig.contains("$$Lambda$");
-      }
-
       private static boolean isLambdaMethod(VmMirror mirror, Location location) {
         String methodName = mirror.getMethodName(location.classID, location.methodID);
         return methodName.startsWith("lambda$");
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
index 11dff7b..18913cc 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -103,7 +103,7 @@
                           MINIFIED_LINE_POSITION,
                           20,
                           FILENAME_INLINE));
-              RetraceMethodResult retraceResult =
+              RetraceFrameResult retraceResult =
                   throwingSubject
                       .streamInstructions()
                       .filter(InstructionSubject::isThrow)
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
index ae533a4..8c2ef82 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -93,7 +93,7 @@
                           INLINED_DEX_PC,
                           23,
                           FILENAME_MAIN));
-              RetraceMethodResult retraceResult =
+              RetraceFrameResult retraceResult =
                   mainSubject
                       .streamInstructions()
                       .filter(InstructionSubject::isThrow)
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index d4346cc..b4a4c48 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -69,7 +71,7 @@
         .minification(minify)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkNoInternalSyntheticNames);
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics);
   }
 
   @Test
@@ -79,7 +81,7 @@
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkNoInternalSyntheticNames)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
         .inspect(this::checkExpectedSynthetics);
   }
 
@@ -133,7 +135,7 @@
         .writeToZip(out3)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkNoInternalSyntheticNames)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
         .inspect(inspector -> assertEquals(syntheticsInParts, getSyntheticMethods(inspector)));
 
     // Finally do a non-intermediate merge.
@@ -142,7 +144,7 @@
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkNoInternalSyntheticNames)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
         .inspect(
             inspector -> {
               if (intermediate) {
@@ -179,16 +181,28 @@
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkNoInternalSyntheticNames)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
         .inspect(this::checkExpectedSynthetics);
   }
 
-  private void checkNoInternalSyntheticNames(CodeInspector inspector) {
+  private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
           assertThat(
               clazz.getFinalName(),
               not(containsString(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
+          if (!clazz.getOriginalName().equals(MiniAssert.class.getTypeName())) {
+            clazz.forAllMethods(
+                method ->
+                    assertTrue(
+                        "Unexpected static invoke to java.lang method:\n"
+                            + method.getMethod().codeToString(),
+                        method
+                            .streamInstructions()
+                            .filter(InstructionSubject::isInvokeStatic)
+                            .noneMatch(
+                                i -> i.getMethod().qualifiedName().startsWith("java.lang"))));
+          }
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
index 42582e6..cf0d8ef 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -4,15 +4,10 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary;
 
-import static junit.framework.TestCase.assertTrue;
-
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 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.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
@@ -45,17 +40,12 @@
 
   @Test
   public void testCustomCollectionD8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary
-            && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
-            && !parameters.getDexRuntimeVersion().isDefault());
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(CustomCollectionForwardingTest.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
-        .inspect(this::assertForwardingMethods)
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
             parameters.getApiLevel(),
@@ -85,53 +75,6 @@
             StringUtils.lines("false", "false", "false", "false", "false", "false"));
   }
 
-  private void assertForwardingMethods(CodeInspector inspector) {
-    if (parameters.getApiLevel().getLevel() >= AndroidApiLevel.N.getLevel()) {
-      return;
-    }
-    ClassSubject cal = inspector.clazz(CustomArrayList.class);
-    MethodSubject spliteratorCal = cal.method("j$.util.Spliterator", "spliterator");
-    assertTrue(spliteratorCal.isPresent());
-    assertTrue(
-        spliteratorCal
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
-    MethodSubject streamCal = cal.method("j$.util.stream.Stream", "stream");
-    assertTrue(streamCal.isPresent());
-    assertTrue(
-        streamCal
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-
-    ClassSubject clhs = inspector.clazz(CustomLinkedHashSet.class);
-    MethodSubject spliteratorClhs = clhs.method("j$.util.Spliterator", "spliterator");
-    assertTrue(spliteratorClhs.isPresent());
-    assertTrue(
-        spliteratorClhs
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("DesugarLinkedHashSet")));
-    MethodSubject streamClhs = clhs.method("j$.util.stream.Stream", "stream");
-    assertTrue(streamClhs.isPresent());
-    assertTrue(
-        streamClhs
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-
-    ClassSubject cl = inspector.clazz(CustomList.class);
-    MethodSubject spliteratorCl = cl.method("j$.util.Spliterator", "spliterator");
-    assertTrue(spliteratorCl.isPresent());
-    assertTrue(
-        spliteratorCl
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("List$-CC")));
-    MethodSubject streamCl = cl.method("j$.util.stream.Stream", "stream");
-    assertTrue(streamCl.isPresent());
-    assertTrue(
-        streamCl
-            .streamInstructions()
-            .anyMatch(i -> i.isInvokeStatic() && i.toString().contains("Collection$-CC")));
-  }
-
   static class Executor {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index ce07cbf..ac48a3d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -56,8 +56,6 @@
 
   @Test
   public void testCustomCollectionSuperCallsR8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     R8TestRunResult r8TestRunResult =
         testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 2330854..3df7246 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -24,7 +24,6 @@
 import java.util.stream.Stream;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -60,21 +59,18 @@
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .assertNoMessages()
-            .inspect(
-                inspector -> {
-                  this.assertCustomCollectionCallsCorrect(inspector, false);
-                })
+            .inspect(this::assertCustomCollectionCallsCorrect)
             .addDesugaredCoreLibraryRunClassPath(
                 this::buildDesugaredLibrary,
                 parameters.getApiLevel(),
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
-            .run(parameters.getRuntime(), EXECUTOR)
+            .run(parameters.getRuntime(), Executor.class)
             .assertSuccess();
-    assertResultCorrect(d8TestRunResult.getStdOut(), d8TestRunResult.getStdErr());
+    assertResultCorrect(d8TestRunResult.getStdOut());
   }
 
-  private void assertResultCorrect(String stdOut, String stdErr) {
+  private void assertResultCorrect(String stdOut) {
     if (requiresEmulatedInterfaceCoreLibDesugaring(parameters) && !shrinkDesugaredLibrary) {
       // When shrinking the class names are not printed correctly anymore due to minification.
       // Expected output is emulated interfaces expected output.
@@ -92,29 +88,20 @@
             .addKeepClassAndMembersRules(Executor.class)
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
-            .inspect(
-                inspector -> {
-                  this.assertCustomCollectionCallsCorrect(inspector, true);
-                })
+            .inspect(this::assertCustomCollectionCallsCorrect)
             .addDesugaredCoreLibraryRunClassPath(
                 this::buildDesugaredLibrary,
                 parameters.getApiLevel(),
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
-            .run(parameters.getRuntime(), EXECUTOR)
+            .run(parameters.getRuntime(), Executor.class)
             .assertSuccess();
-    assertResultCorrect(r8TestRunResult.getStdOut(), r8TestRunResult.getStdErr());
+    assertResultCorrect(r8TestRunResult.getStdOut());
   }
 
-  private void assertCustomCollectionCallsCorrect(CodeInspector inspector, boolean r8) {
+  private void assertCustomCollectionCallsCorrect(CodeInspector inspector) {
     MethodSubject direct = inspector.clazz(EXECUTOR).uniqueMethodWithName("directTypes");
-    // TODO(b/134732760): Due to memberRebinding, R8 is not as precise as D8 regarding
-    // desugaring of invokes. This will be fixed when creation of desugared method is moved
-    // ahead of R8 compilation pipeline.
-    if (!r8) {
-      Assert.assertFalse(
-          direct.streamInstructions().anyMatch(instr -> instr.toString().contains("$-EL")));
-    } else if (requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+    if (requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
       assertTrue(
           direct
               .streamInstructions()
@@ -140,22 +127,17 @@
                 instr ->
                     instr.toString().contains("$-EL")
                         || instr.toString().contains("Comparator$-CC")));
-    inherited.streamInstructions().forEach(x -> assertInheritedDispatchCorrect(x, r8));
+    inherited.streamInstructions().forEach(this::assertInheritedDispatchCorrect);
   }
 
-  private void assertInheritedDispatchCorrect(InstructionSubject instructionSubject, boolean r8) {
+  private void assertInheritedDispatchCorrect(InstructionSubject instructionSubject) {
     if (!instructionSubject.isConstString(JumboStringMode.ALLOW)) {
-      for (String s : new String[] {"stream", "parallelStream", "spliterator", "sort"}) {
+      for (String s : new String[] {">stream", "spliterator", "sort"}) {
         if (instructionSubject.toString().contains(s)) {
-          if (!r8 || instructionSubject.isInvokeStatic()) {
             assertTrue(instructionSubject.isInvokeStatic());
             assertTrue(
                 instructionSubject.toString().contains("$-EL")
                     || instructionSubject.toString().contains("Comparator$-CC"));
-          } else {
-            // Has been devirtualized.
-            assertTrue(instructionSubject.isInvokeVirtual());
-          }
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index d53e0e6..7c46fe3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static com.android.tools.r8.MarkerMatcher.assertMarkersMatch;
+import static com.android.tools.r8.MarkerMatcher.markerBackend;
 import static com.android.tools.r8.MarkerMatcher.markerCompilationMode;
 import static com.android.tools.r8.MarkerMatcher.markerHasDesugaredLibraryIdentifier;
 import static com.android.tools.r8.MarkerMatcher.markerIsDesugared;
@@ -138,6 +139,7 @@
         allOf(
             markerTool(Tool.D8),
             markerCompilationMode(CompilationMode.DEBUG),
+            markerBackend(Backend.CF),
             markerIsDesugared(),
             markerMinApi(parameters.getApiLevel()),
             not(markerHasDesugaredLibraryIdentifier()));
@@ -152,6 +154,8 @@
             .writeToZip();
 
     // D8 dex file output marker has the same marker as the D8 class file output.
+    // TODO(b/166617364): They should not be the same after backend is recorded and neither has
+    //  library desugaring info.
     assertMarkersMatch(ExtractMarker.extractMarkerFromJarFile(desugaredLibDex), markerMatcher);
 
     // Build an app using library desugaring merging with library not using library desugaring.
@@ -164,7 +168,18 @@
               .compile()
               .writeToZip();
 
-      assertMarkersMatch(ExtractMarker.extractMarkerFromDexFile(app), markerMatcher);
+      // When there is no class-file resources we are adding the marker for the last compilation.
+      assertMarkersMatch(
+          ExtractMarker.extractMarkerFromDexFile(app),
+          ImmutableList.of(
+              markerMatcher,
+              allOf(
+                  markerTool(Tool.D8),
+                  markerCompilationMode(CompilationMode.DEBUG),
+                  markerBackend(Backend.DEX),
+                  markerIsDesugared(),
+                  markerMinApi(parameters.getApiLevel()),
+                  not(markerHasDesugaredLibraryIdentifier()))));
     } catch (CompilationFailedException e) {
       assertTrue(someLibraryDesugaringRequired());
       return;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
new file mode 100644
index 0000000..a6ea994
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
@@ -0,0 +1,200 @@
+// Copyright (c) 2019, 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.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+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 NoDesugaredLibraryDexFileTest 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().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public NoDesugaredLibraryDexFileTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCustomCollectionD8() throws Exception {
+    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(NoDesugaredLibraryDexFileTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::assertNoForwardingStreamMethod)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("1", "0");
+    assertTrue(keepRuleConsumer.get().isEmpty());
+  }
+
+  @Test
+  public void testCustomCollectionR8() throws Exception {
+    Assume.assumeTrue(requiresEmulatedInterfaceCoreLibDesugaring(parameters));
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NoDesugaredLibraryDexFileTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Executor.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::assertNoForwardingStreamMethod)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("1", "0");
+    assertTrue(keepRuleConsumer.get().isEmpty());
+  }
+
+  private void assertNoForwardingStreamMethod(CodeInspector inspector) {
+    assertTrue(inspector.clazz(CustomArrayList.class).uniqueMethodWithName("stream").isAbsent());
+    assertTrue(inspector.clazz(CustomSortedSet.class).uniqueMethodWithName("stream").isAbsent());
+  }
+
+  static class Executor {
+
+    // No method here is using any emulated interface default method, so, there is no need for
+    // the desugared library dex file despite desugared library being enabled.
+    public static void main(String[] args) {
+      ArrayList<Object> cArrayList = new CustomArrayList<>();
+      SortedSet<Object> cSortedSet = new CustomSortedSet<>();
+      cArrayList.add(1);
+      cSortedSet.add(1);
+      System.out.println(cArrayList.size());
+      System.out.println(cSortedSet.size());
+    }
+  }
+
+  // Extends directly a core library class which implements other library interfaces.
+  private static class CustomArrayList<E> extends ArrayList<E> {}
+
+  // Implements directly a core library interface which implements other library interfaces.
+  static class CustomSortedSet<E> implements SortedSet<E> {
+
+    @Nullable
+    @Override
+    public Comparator<? super E> comparator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public SortedSet<E> subSet(E fromElement, E toElement) {
+      return new CustomSortedSet<>();
+    }
+
+    @NotNull
+    @Override
+    public SortedSet<E> headSet(E toElement) {
+      return new CustomSortedSet<>();
+    }
+
+    @NotNull
+    @Override
+    public SortedSet<E> tailSet(E fromElement) {
+      return new CustomSortedSet<>();
+    }
+
+    @Override
+    public E first() {
+      return null;
+    }
+
+    @Override
+    public E last() {
+      return null;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return Collections.emptyIterator();
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] a) {
+      return a;
+    }
+
+    @Override
+    public boolean add(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+
+    @Override
+    public boolean removeAll(@NotNull Collection c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection c) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection c) {
+      return false;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
new file mode 100644
index 0000000..98ad7dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
@@ -0,0 +1,206 @@
+// Copyright (c) 2019, 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.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.SortedSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+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 NoForwardingMethodsTest 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().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public NoForwardingMethodsTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCustomCollectionD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(NoForwardingMethodsTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::assertNoForwardingStreamMethod)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("str:1", "0");
+  }
+
+  @Test
+  public void testCustomCollectionR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(NoForwardingMethodsTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Executor.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::assertNoForwardingStreamMethod)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("str:1", "0");
+  }
+
+  private void assertNoForwardingStreamMethod(CodeInspector inspector) {
+    assertTrue(inspector.clazz(CustomArrayList.class).uniqueMethodWithName("stream").isAbsent());
+    assertTrue(inspector.clazz(CustomSortedSet.class).uniqueMethodWithName("stream").isAbsent());
+  }
+
+  static class Executor {
+
+    // The main method is using stream, but since there are no overrides, the classes should not
+    // have any forwarding method.
+    public static void main(String[] args) {
+      ArrayList<Object> cArrayList = new CustomArrayList<>();
+      SortedSet<Object> cSortedSet = new CustomSortedSet<>();
+      cArrayList.add(1);
+      cSortedSet.add(1);
+      System.out.println(cArrayList.stream().map(i -> "str:" + i).toArray()[0]);
+      System.out.println(cSortedSet.stream().filter(Objects::isNull).count());
+    }
+  }
+
+  // Extends directly a core library class which implements other library interfaces.
+  private static class CustomArrayList<E> extends ArrayList<E> {}
+
+  // Implements directly a core library interface which implements other library interfaces.
+  static class CustomSortedSet<E> implements SortedSet<E> {
+
+    @Nullable
+    @Override
+    public Comparator<? super E> comparator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public SortedSet<E> subSet(E fromElement, E toElement) {
+      return new CustomSortedSet<>();
+    }
+
+    @NotNull
+    @Override
+    public SortedSet<E> headSet(E toElement) {
+      return new CustomSortedSet<>();
+    }
+
+    @NotNull
+    @Override
+    public SortedSet<E> tailSet(E fromElement) {
+      return new CustomSortedSet<>();
+    }
+
+    @Override
+    public E first() {
+      return null;
+    }
+
+    @Override
+    public E last() {
+      return null;
+    }
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return Collections.emptyIterator();
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] a) {
+      return a;
+    }
+
+    @Override
+    public boolean add(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+
+    @Override
+    public boolean removeAll(@NotNull Collection c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection c) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection c) {
+      return false;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 3268a35..9d9b5cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -181,8 +181,6 @@
 
   @Test
   public void testStream() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(
-        shrinkDesugaredLibrary && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
     Assume.assumeFalse(
         "getAllFilesWithSuffixInDirectory() seems to find different files on Windows",
         ToolHelper.isWindows());
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index cbd5710..a04b0c1 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -97,7 +97,6 @@
 
   @Test
   public void testHello() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     Assume.assumeTrue(!ToolHelper.isWindows());
     Path prevGeneratedJar = null;
     String prevRunResult = null;
@@ -131,7 +130,6 @@
 
   @Test
   public void testR8() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     Assume.assumeTrue(!ToolHelper.isWindows());
     Assume.assumeTrue(parameters.isCfRuntime());
     Assume.assumeTrue(CfVm.JDK11 == parameters.getRuntime().asCf().getVm());
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index e1d892d..762916e 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -47,7 +47,6 @@
 
   @Test
   public void testR8CompiledWithR8() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
index 25a3434..0f313cf 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/NullCheckEnumUnboxingTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.List;
 import java.util.Objects;
 import org.junit.Test;
@@ -51,13 +50,10 @@
             .inspectDiagnosticMessages(
                 m -> {
                   assertEnumIsUnboxed(MyEnum.class, MyEnum.class.getSimpleName(), m);
-                  // MyEnum19 is unboxed only if minAPI > 19 because Objects#requiredNonNull is then
-                  // present.
-                  if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)) {
-                    assertEnumIsUnboxed(MyEnum19.class, MyEnum19.class.getSimpleName(), m);
-                  } else {
-                    assertEnumIsBoxed(MyEnum19.class, MyEnum19.class.getSimpleName(), m);
-                  }
+                  // MyEnum19 is always unboxed. If minAPI > 19 the unboxer will identify
+                  // Objects#requiredNonNull usage. For 19 and prior, the backport code should not
+                  // prohibit the unboxing either.
+                  assertEnumIsUnboxed(MyEnum19.class, MyEnum19.class.getSimpleName(), m);
                 })
             .run(parameters.getRuntime(), MainNullTest.class)
             .assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
new file mode 100644
index 0000000..58812ee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
@@ -0,0 +1,169 @@
+// 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.genericsignature;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.lang.reflect.Method;
+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 UnboundedFormalTypeGenericSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String SUPER_BINARY_NAME =
+      DescriptorUtils.getBinaryNameFromJavaType(Super.class.getTypeName());
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnboundedFormalTypeGenericSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(
+            transformer(Main.class).removeInnerClasses().transform(),
+            transformer(Super.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "R", "T");
+  }
+
+  @Test
+  public void testUnboundParametersInClassRuntime() throws Exception {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature("L" + SUPER_BINARY_NAME + "<TR;>;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<R>", "R", "T");
+    }
+  }
+
+  @Test
+  public void testUnboundParametersInMethodRuntime() throws Exception {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature(
+                        MethodPredicate.onName("testStatic"), "<R:Ljava/lang/Object;>()TS;")
+                    .setGenericSignature(
+                        MethodPredicate.onName("testVirtual"), "<R:Ljava/lang/Object;>()TQ;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "null", "null");
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "S", "Q");
+    }
+  }
+
+  @Test
+  public void testUnboundParametersInClassR8() throws Exception {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature("L" + SUPER_BINARY_NAME + "<TR;>;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .addKeepAllClassesRule()
+            .addKeepAttributes(
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<R>", "R", "T");
+    }
+  }
+
+  @Test
+  public void testUnboundParametersInMethodR8() throws Exception {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(
+                transformer(Main.class)
+                    .removeInnerClasses()
+                    .setGenericSignature(
+                        MethodPredicate.onName("testStatic"), "<R:Ljava/lang/Object;>()TS;")
+                    .setGenericSignature(
+                        MethodPredicate.onName("testVirtual"), "<R:Ljava/lang/Object;>()TQ;")
+                    .transform(),
+                transformer(Super.class).removeInnerClasses().transform())
+            .addKeepAllClassesRule()
+            .addKeepAttributes(
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
+            .setMinApi(parameters.getApiLevel())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.isCfRuntime()) {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "null", "null");
+    } else {
+      runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "S", "Q");
+    }
+  }
+
+  public static class Super<T> {}
+
+  public static class Main<T> extends Super<T> {
+
+    public static <R extends Super<R>> void main(String[] args) throws NoSuchMethodException {
+      System.out.println(Main.class.getGenericSuperclass());
+      testStatic();
+      new Main<>().testVirtual();
+    }
+
+    private static <R> R testStatic() {
+      try {
+        Method testStatic = Main.class.getDeclaredMethod("testStatic");
+        System.out.println(testStatic.getGenericReturnType());
+        return null;
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    private T testVirtual() {
+      try {
+        Method testVirtual = Main.class.getDeclaredMethod("testVirtual");
+        System.out.println(testVirtual.getGenericReturnType());
+        return null;
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java
new file mode 100644
index 0000000..4c2ac5e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java
@@ -0,0 +1,83 @@
+// 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.genericsignature;
+
+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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+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 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 UnknownClassInSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String NEW_CLASS_SIGNATURE = "<T:LUnknownClass1;>LUnknownClass2<LUnknownClass3;>;";
+  private final String NEW_FIELD_SIGNATURE = "LUnknownClass4<LUnknownClass4;>;";
+  private final String NEW_METHOD_SIGNATURE = "()LUnkownClass5<LunknownPackage/UnknownClass6;>;";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnknownClassInSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .removeInnerClasses()
+                .setGenericSignature(NEW_CLASS_SIGNATURE)
+                .setGenericSignature(FieldPredicate.onName("field"), NEW_FIELD_SIGNATURE)
+                .setGenericSignature(MethodPredicate.onName("main"), NEW_METHOD_SIGNATURE)
+                .transform())
+        .addKeepAllClassesRule()
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.ENCLOSING_METHOD,
+            ProguardKeepAttributes.INNER_CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(Main.class);
+              assertThat(clazz, isPresent());
+              assertEquals(NEW_CLASS_SIGNATURE, clazz.getFinalSignatureAttribute());
+              FieldSubject field = clazz.uniqueFieldWithFinalName("field");
+              assertThat(field, isPresent());
+              assertEquals(NEW_FIELD_SIGNATURE, field.getFinalSignatureAttribute());
+              MethodSubject method = clazz.uniqueMethodWithFinalName("main");
+              assertThat(method, isPresent());
+              assertEquals(NEW_METHOD_SIGNATURE, method.getFinalSignatureAttribute());
+            });
+  }
+
+  public static class Main {
+
+    private List<Main> field;
+
+    public static void main(String[] args) {
+      System.out.println("Hello World!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/NonFinalFinalFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/NonFinalFinalFieldTest.java
new file mode 100644
index 0000000..292f74e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/NonFinalFinalFieldTest.java
@@ -0,0 +1,170 @@
+// 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.ir.optimize.fields;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.transformers.MethodTransformer;
+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 NonFinalFinalFieldTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public NonFinalFinalFieldTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("2", "2", "2");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("2", "2", "2");
+  }
+
+  private List<byte[]> getProgramClassFileData() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class)
+            .setAccessFlags(A.class.getDeclaredField("f"), AccessFlags::setFinal)
+            .transform(),
+        transformer(B.class)
+            .setAccessFlags(B.class.getDeclaredField("f"), AccessFlags::setFinal)
+            .addMethodTransformer(createRemoveObjectInitMethodTransformer())
+            .transform(),
+        transformer(C.class)
+            .setAccessFlags(C.class.getDeclaredField("f"), AccessFlags::setFinal)
+            .addMethodTransformer(createRemoveObjectInitMethodTransformer())
+            .transform());
+  }
+
+  private MethodTransformer createRemoveObjectInitMethodTransformer() {
+    return new MethodTransformer() {
+
+      private boolean seenInit = false;
+
+      @Override
+      public void visitMethodInsn(
+          int opcode, String owner, String name, String descriptor, boolean isInterface) {
+        if (isDefaultConstructor()) {
+          if (name.equals("<init>")) {
+            seenInit = true;
+            return;
+          }
+          super.visitMethodInsn(
+              opcode, owner, name.equals("init") ? "<init>" : name, descriptor, isInterface);
+        } else {
+          super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+        }
+      }
+
+      @Override
+      public void visitVarInsn(int opcode, int var) {
+        if (isDefaultConstructor()) {
+          if (seenInit) {
+            super.visitVarInsn(opcode, var);
+          }
+        } else {
+          super.visitVarInsn(opcode, var);
+        }
+      }
+
+      private boolean isDefaultConstructor() {
+        return getContext().getReference().getMethodName().equals("<init>")
+            && getContext().getReference().getFormalTypes().isEmpty();
+      }
+    };
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new A().f);
+      System.out.println(new B().f);
+      System.out.println(new C().f);
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    /*final*/ int f;
+
+    A() {
+      this(1);
+      this.f = 2;
+    }
+
+    A(int f) {
+      this.f = f;
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    /*final*/ int f;
+
+    B() {
+      this.f = 1;
+      init(2); // Rewritten to B(2)
+    }
+
+    B(int f) {
+      this.f = f;
+    }
+
+    private void init(int f) {}
+  }
+
+  @NeverClassInline
+  static class C {
+
+    /*final*/ int f;
+
+    C() {
+      this.f = 1;
+      init(2, true); // Rewritten to B(2, true)
+    }
+
+    C(int f, boolean b) {
+      if (b) {
+        this.f = f;
+      }
+    }
+
+    private void init(int f, boolean b) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.java
new file mode 100644
index 0000000..95fef57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMethodWithRetargetedLibMemberTest.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.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Arrays;
+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 InlineMethodWithRetargetedLibMemberTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestBase.getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public InlineMethodWithRetargetedLibMemberTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              // TODO(b/171197204): Method should be inlined.
+              assertThat(
+                  inspector.clazz(TestClass.class).uniqueMethodWithName("test"),
+                  notIf(
+                      isPresent(),
+                      parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)));
+            });
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      test(args);
+    }
+
+    static void test(String[] args) {
+      Arrays.stream(args);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index a684ea3..2012c6f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.AlwaysInline;
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -41,7 +42,6 @@
 
   @Test
   public void test() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     testForR8(parameters.getBackend())
         .addInnerClasses(SingleTargetAfterInliningTest.class)
         .addKeepMainRule(TestClass.class)
@@ -52,6 +52,7 @@
             })
         .enableAlwaysInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .enableSideEffectAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -118,6 +119,7 @@
   }
 
   @NeverClassInline
+  @NoHorizontalClassMerging
   static class C extends A {
 
     @AssumeMayHaveSideEffects // To ensure that new C() cannot be removed.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 406e16d..1452fd4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -33,6 +34,7 @@
 
   static class Sub extends Base {}
 
+  @NoHorizontalClassMerging
   static class EffectivelyFinal {}
 
   static class Reflection implements Callable<Class<?>> {
@@ -208,7 +210,6 @@
   @Test
   public void testR8() throws Exception {
     boolean isRelease = mode == CompilationMode.RELEASE;
-    expectThrowsWithHorizontalClassMergingIf(isRelease);
     boolean expectCallPresent = !isRelease;
     int expectedGetClassCount = isRelease ? 0 : 5;
     int expectedConstClassCount = isRelease ? (parameters.isCfRuntime() ? 8 : 6) : 1;
@@ -216,6 +217,7 @@
         .setMode(mode)
         .addInnerClasses(GetClassTest.class)
         .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .noMinification()
         .addOptionsModification(this::configure)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
index 27b1a0c..86e30dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
@@ -36,7 +36,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public SwitchCaseRemovalTest(TestParameters parameters) {
@@ -59,7 +59,7 @@
             })
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::verifyOutput)
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index 179333f..2cbef3e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -36,7 +36,6 @@
 
   @Test
   public void testCompanionAndRegularObjects() throws Exception {
-    expectThrowsWithHorizontalClassMerging();
     assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_staticizer.MainKt";
 
@@ -47,7 +46,6 @@
         false,
         app -> {
           CodeInspector inspector = new CodeInspector(app);
-          assertThat(inspector.clazz("class_staticizer.Regular$Companion"), isPresent());
           assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent());
 
           // The Util class is there, but its instance methods have been inlined.
@@ -67,7 +65,6 @@
         app -> {
           CodeInspector inspector = new CodeInspector(app);
           assertThat(inspector.clazz("class_staticizer.Regular$Companion"), not(isPresent()));
-          assertThat(inspector.clazz("class_staticizer.Derived$Companion"), not(isPresent()));
 
           ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
           assertThat(utilClass, isPresent());
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
index 84ee93d..446d982 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -35,7 +35,6 @@
 
   @Test
   public void testJStyleRunnable() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(allowAccessModification);
     final String mainClassName = "lambdas_jstyle_runnable.MainKt";
     runTest("lambdas_jstyle_runnable", mainClassName, optionsModifier, null);
   }
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
deleted file mode 100644
index 59cfbe8..0000000
--- a/src/test/java/com/android/tools/r8/regress/B76025099.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.regress;
-
-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.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.function.Function;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class B76025099 extends TestBase {
-
-  private Backend backend;
-
-  @Parameterized.Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
-  }
-
-  public B76025099(Backend backend) {
-    this.backend = backend;
-  }
-
-  private static final String PRG =
-      ToolHelper.EXAMPLES_BUILD_DIR + "regress_76025099" + FileUtils.JAR_EXTENSION;
-
-  private AndroidApp runR8(AndroidApp app) throws Exception {
-    R8Command command =
-        ToolHelper.addProguardConfigurationConsumer(
-                ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend)),
-                pgConfig -> {
-                  pgConfig.setPrintMapping(true);
-                  pgConfig.setPrintMappingFile(map);
-                })
-            .addLibraryFiles(runtimeJar(backend))
-            .addProguardConfigurationFiles(pgConfig)
-            .setDisableMinification(true)
-            .setOutput(tempRoot.toPath(), outputMode(backend))
-            .build();
-    return ToolHelper.runR8(command);
-  }
-
-  private File tempRoot;
-  private Path jarPath;
-  private AndroidApp originalApp;
-  private String mainName;
-  private Path pgConfig;
-  private Path map;
-
-  @Before
-  public void setUp() throws Exception {
-    tempRoot = temp.getRoot();
-    jarPath = Paths.get(PRG);
-    originalApp = readJar(jarPath);
-    mainName = "regress_76025099.Main";
-    pgConfig = File.createTempFile("keep-rules", ".config", tempRoot).toPath();
-    String config = keepMainProguardConfiguration(mainName);
-    config += System.lineSeparator() + "-dontobfuscate";
-    Files.write(pgConfig, config.getBytes());
-    map = File.createTempFile("proguard", ".map", tempRoot).toPath();
-  }
-
-  @Test
-  public void testProguardAndD8() throws Exception {
-    Assume.assumeTrue(backend == Backend.DEX);
-    if (!isRunProguard()) {
-      return;
-    }
-
-    ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
-    assertEquals(0, jvmOutput.exitCode);
-
-    Path proguarded =
-        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, tempRoot).toPath();
-    ProcessResult proguardResult = ToolHelper.runProguardRaw(jarPath, proguarded, pgConfig, map);
-    assertEquals(0, proguardResult.exitCode);
-
-    AndroidApp processedApp = ToolHelper.runD8(readJar(proguarded));
-    verifyFieldAccess(processedApp, jvmOutput);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
-    assertEquals(0, jvmOutput.exitCode);
-
-    AndroidApp processedApp = runR8(originalApp);
-    verifyFieldAccess(processedApp, jvmOutput);
-  }
-
-  private static InstructionSubject findInstructionOrNull(
-      Iterator<InstructionSubject> iterator, Function<InstructionSubject, Boolean> predicate) {
-    while (iterator.hasNext()) {
-      InstructionSubject instruction = iterator.next();
-      if (predicate.apply(instruction)) {
-        return instruction;
-      }
-    }
-    return null;
-  }
-
-  private void verifyFieldAccess(AndroidApp processedApp, ProcessResult jvmOutput)
-      throws Exception {
-    CodeInspector inspector = new CodeInspector(processedApp);
-    ClassSubject impl = inspector.clazz("regress_76025099.impl.Impl");
-    assertThat(impl, isPresent());
-    MethodSubject init = impl.init("java.lang.String");
-    assertThat(init, isPresent());
-    Iterator<InstructionSubject> iterator = init.iterateInstructions();
-
-    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isInvoke));
-
-    InstructionSubject instruction =
-        findInstructionOrNull(iterator, InstructionSubject::isInstancePut);
-    assertNotNull(instruction);
-    FieldAccessInstructionSubject fieldAccessInstruction =
-        (FieldAccessInstructionSubject) instruction;
-    assertEquals("name", fieldAccessInstruction.name());
-    assertTrue(fieldAccessInstruction.holder().is(impl.getDexProgramClass().type.toString()));
-
-    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isReturnVoid));
-
-    ProcessResult output;
-    if (backend == Backend.DEX) {
-      output = runOnArtRaw(processedApp, mainName);
-    } else {
-      assert backend == Backend.CF;
-      output = runOnJavaRaw(processedApp, mainName, Collections.emptyList());
-    }
-    assertEquals(0, output.exitCode);
-    assertEquals(jvmOutput.stdout, output.stdout);
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java b/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
index 0d2f1d5..541ca47 100644
--- a/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.transformers.MethodTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -63,7 +64,7 @@
     String oldLambdaName = "lambda$identity$0";
     String newLambdaName = "lambda$identity$foo";
     return transformer(Function.class)
-        .renameMethod(oldLambdaName, newLambdaName)
+        .renameMethod(MethodPredicate.onName(oldLambdaName), newLambdaName)
         .addMethodTransformer(
             new MethodTransformer() {
               @Override
diff --git a/src/test/java/com/android/tools/r8/regress/b76025099/B76025099.java b/src/test/java/com/android/tools/r8/regress/b76025099/B76025099.java
new file mode 100644
index 0000000..e179440
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/B76025099.java
@@ -0,0 +1,127 @@
+// 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.regress.b76025099;
+
+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.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardTestCompileResult;
+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.regress.b76025099.testclasses.Main;
+import com.android.tools.r8.regress.b76025099.testclasses.impl.Impl;
+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.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Iterator;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class B76025099 extends TestBase {
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines(Main.class.getTypeName());
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public B76025099(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testProguardAndD8() throws Exception {
+    assumeTrue(isRunProguard());
+
+    ProguardTestCompileResult proguardCompileResult =
+        testForProguard()
+            .addProgramFiles(ToolHelper.getClassFilesForTestPackage(Main.class.getPackage()))
+            .addKeepMainRule(Main.class)
+            .noMinification()
+            .compile();
+
+    if (parameters.isDexRuntime()) {
+      testForD8()
+          .addProgramFiles(proguardCompileResult.outputJar())
+          .setMinApi(parameters.getApiLevel())
+          .compile()
+          .inspect(this::verifyFieldAccess)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    } else {
+      proguardCompileResult
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getClassFilesForTestPackage(Main.class.getPackage()))
+        .addKeepMainRule(Main.class)
+        .enableNoVerticalClassMergingAnnotations()
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::verifyFieldAccess)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private static InstructionSubject findInstructionOrNull(
+      Iterator<InstructionSubject> iterator, Function<InstructionSubject, Boolean> predicate) {
+    while (iterator.hasNext()) {
+      InstructionSubject instruction = iterator.next();
+      if (predicate.apply(instruction)) {
+        return instruction;
+      }
+    }
+    return null;
+  }
+
+  private void verifyFieldAccess(CodeInspector inspector) {
+    ClassSubject impl = inspector.clazz(Impl.class);
+    assertThat(impl, isPresent());
+    MethodSubject init = impl.init("java.lang.String");
+    assertThat(init, isPresent());
+    Iterator<InstructionSubject> iterator = init.iterateInstructions();
+
+    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isInvoke));
+
+    InstructionSubject instruction =
+        findInstructionOrNull(iterator, InstructionSubject::isInstancePut);
+    assertNotNull(instruction);
+    FieldAccessInstructionSubject fieldAccessInstruction =
+        (FieldAccessInstructionSubject) instruction;
+    assertEquals("name", fieldAccessInstruction.name());
+    assertTrue(fieldAccessInstruction.holder().is(impl.getDexProgramClass().type.toString()));
+
+    assertNotNull(findInstructionOrNull(iterator, InstructionSubject::isReturnVoid));
+  }
+}
diff --git a/src/test/examples/regress_76025099/Logger.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Logger.java
similarity index 81%
rename from src/test/examples/regress_76025099/Logger.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Logger.java
index d6e9f95..b0178e0 100644
--- a/src/test/examples/regress_76025099/Logger.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Logger.java
@@ -1,7 +1,7 @@
 // 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 regress_76025099;
+package com.android.tools.r8.regress.b76025099.testclasses;
 
 public interface Logger {
   String getName();
diff --git a/src/test/examples/regress_76025099/Main.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Main.java
similarity index 74%
rename from src/test/examples/regress_76025099/Main.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Main.java
index 659bdbd..3d49201 100644
--- a/src/test/examples/regress_76025099/Main.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/Main.java
@@ -1,9 +1,9 @@
 // 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 regress_76025099;
+package com.android.tools.r8.regress.b76025099.testclasses;
 
-import regress_76025099.impl.Factory;
+import com.android.tools.r8.regress.b76025099.testclasses.impl.Factory;
 
 public class Main {
   public static void main(String[] args) {
diff --git a/src/test/examples/regress_76025099/helper/AbstractBase.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractBase.java
similarity index 62%
rename from src/test/examples/regress_76025099/helper/AbstractBase.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractBase.java
index 046e12f..9d5694c 100644
--- a/src/test/examples/regress_76025099/helper/AbstractBase.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractBase.java
@@ -1,10 +1,12 @@
 // 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 regress_76025099.helper;
+package com.android.tools.r8.regress.b76025099.testclasses.helper;
 
-import regress_76025099.Logger;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.regress.b76025099.testclasses.Logger;
 
+@NoVerticalClassMerging
 abstract class AbstractBase implements Logger {
   protected String name;
 
diff --git a/src/test/examples/regress_76025099/helper/AbstractSub.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractSub.java
similarity index 63%
rename from src/test/examples/regress_76025099/helper/AbstractSub.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractSub.java
index 9240e4a..0dd1af7 100644
--- a/src/test/examples/regress_76025099/helper/AbstractSub.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/helper/AbstractSub.java
@@ -1,7 +1,6 @@
 // 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 regress_76025099.helper;
+package com.android.tools.r8.regress.b76025099.testclasses.helper;
 
-public abstract class AbstractSub extends AbstractBase {
-}
+public abstract class AbstractSub extends AbstractBase {}
diff --git a/src/test/examples/regress_76025099/impl/Factory.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Factory.java
similarity index 82%
rename from src/test/examples/regress_76025099/impl/Factory.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Factory.java
index 0cc1cc9..fd98f42 100644
--- a/src/test/examples/regress_76025099/impl/Factory.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Factory.java
@@ -1,7 +1,7 @@
 // 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 regress_76025099.impl;
+package com.android.tools.r8.regress.b76025099.testclasses.impl;
 
 public class Factory {
   public static Impl getImpl(String name) {
diff --git a/src/test/examples/regress_76025099/impl/Impl.java b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Impl.java
similarity index 67%
rename from src/test/examples/regress_76025099/impl/Impl.java
rename to src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Impl.java
index 74eefcf..b24bde9 100644
--- a/src/test/examples/regress_76025099/impl/Impl.java
+++ b/src/test/java/com/android/tools/r8/regress/b76025099/testclasses/impl/Impl.java
@@ -1,12 +1,12 @@
 // 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 regress_76025099.impl;
+package com.android.tools.r8.regress.b76025099.testclasses.impl;
 
-import regress_76025099.helper.AbstractSub;
+import com.android.tools.r8.regress.b76025099.testclasses.helper.AbstractSub;
 
 public class Impl extends AbstractSub {
   Impl(String name) {
     this.name = name;
   }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 0afee04..c1f2301 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -86,7 +86,6 @@
 
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
-    expectThrowsWithHorizontalClassMergingIf(parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addInnerClasses(TargetInDefaultMethodTest.class)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 1fa811a..0beee59 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -120,7 +120,7 @@
       MethodSubject mainSubject,
       LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
-    RetraceMethodResult retraceResult =
+    RetraceFrameResult retraceResult =
         mainSubject
             .streamInstructions()
             .filter(InstructionSubject::isThrow)
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 05b028a..3b0538d 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -225,7 +225,7 @@
       MethodSubject mainSubject,
       LinePosition inlineStack) {
     assertThat(mainSubject, isPresent());
-    RetraceMethodResult retraceResult =
+    RetraceFrameResult retraceResult =
         mainSubject
             .streamInstructions()
             .filter(InstructionSubject::isThrow)
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 85e6cbc..2074afa 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper;
@@ -176,6 +177,12 @@
     assertTrue(processResult.stdout.startsWith(WAITING_MESSAGE));
   }
 
+  @Test
+  public void testHelpMessageWithQuiet() throws IOException {
+    ProcessResult processResult = runRetrace("", "", true, "--quiet");
+    assertFalse(processResult.stdout.startsWith(WAITING_MESSAGE));
+  }
+
   private final String nonMappableStackTrace =
       StringUtils.lines(
           "com.android.r8.R8Exception: Problem when compiling program",
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 bffcd14..1b6735f 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -43,6 +44,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -107,16 +109,7 @@
   @Test
   public void testInvalidStackTraceLineWarnings() {
     InvalidStackTrace invalidStackTraceTest = new InvalidStackTrace();
-    TestDiagnosticMessagesImpl diagnosticsHandler = runRetraceTest(invalidStackTraceTest);
-    if (!useRegExpParsing) {
-      diagnosticsHandler.assertOnlyWarnings();
-      diagnosticsHandler.assertWarningsCount(invalidStackTraceTest.expectedWarnings());
-      assertThat(
-          diagnosticsHandler.getWarnings().get(0).getDiagnosticMessage(),
-          containsString(". . . 7 more"));
-    } else {
-      diagnosticsHandler.assertNoMessages();
-    }
+    runRetraceTest(invalidStackTraceTest).assertNoMessages();
   }
 
   @Test
@@ -136,11 +129,15 @@
 
   @Test
   public void testAmbiguousStackTrace() {
+    // TODO(b/170797525): Remove when we have a fixed ordering.
+    assumeTrue(useRegExpParsing);
     runRetraceTest(new AmbiguousStackTrace());
   }
 
   @Test
   public void testAmbiguousMissingLineStackTrace() {
+    // TODO(b/170797525): Remove when we have a fixed ordering.
+    assumeTrue(useRegExpParsing);
     runRetraceTest(new AmbiguousMissingLineStackTrace());
   }
 
@@ -178,6 +175,7 @@
   }
 
   @Test
+  @Ignore("b/170293908")
   public void testBootLoaderAndNamedModulesStackTrace() {
     assumeFalse(useRegExpParsing);
     runRetraceTest(new NamedModuleStackTrace());
@@ -185,6 +183,8 @@
 
   @Test
   public void testUnknownSourceStackTrace() {
+    // TODO(b/170797525): Remove when we have a fixed ordering.
+    assumeTrue(useRegExpParsing);
     runRetraceTest(new UnknownSourceStackTrace());
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
index fb65c1d..3e5b4ca 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceVerboseTests.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.retrace.Retrace.DEFAULT_REGULAR_EXPRESSION;
 import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -57,6 +58,8 @@
   }
 
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest) {
+    // TODO(b/170293906): Remove assumption.
+    assumeTrue(useRegExpParsing);
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     RetraceCommand retraceCommand =
         RetraceCommand.builder(diagnosticsHandler)
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
index a36feae..c86411e 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualIdentityStackTrace.java
@@ -56,6 +56,6 @@
 
   @Override
   public int expectedWarnings() {
-    return 1;
+    return 0;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java
index 2c057d3..0e38636 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/ActualRetraceBotStackTrace.java
@@ -68,6 +68,6 @@
 
   @Override
   public int expectedWarnings() {
-    return 1;
+    return 0;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 816c073..4f65b48 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -3,12 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.assumenosideeffects;
 
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Collection;
 import org.junit.Test;
@@ -19,25 +21,6 @@
 public class AssumenosideeffectsPropagationTest extends TestBase {
   private static final Class<?> MAIN = SubsUser.class;
 
-  private static final String JVM_OUTPUT = StringUtils.lines(
-      "[Sub1, info]: message00",
-      "[Base1, debug]: message00",
-      "[Sub1, verbose]: message00",
-      "[Sub1, info]: message1",
-      "[Base1, debug]: message2",
-      "[Sub1, verbose]: message3",
-      "[Base2, info]: message08",
-      "[AnotherSub2, debug]: message08",
-      "[AnotherSub2, verbose]: message08",
-      "[Base2, info]: message4",
-      "[AnotherSub2, debug]: message5",
-      "[AnotherSub2, verbose]: message6",
-      "The end"
-  );
-  private static final String OUTPUT_WITHOUT_MESSAGES = StringUtils.lines(
-      "The end"
-  );
-
   enum TestConfig {
     SPECIFIC_RULES,
     NON_SPECIFIC_RULES_PARTIAL,
@@ -84,33 +67,40 @@
       }
     }
 
-    public String expectedOutput() {
+    public String expectedOutput(boolean enableHorizontalClassMerging) {
       switch (this) {
         case SPECIFIC_RULES:
-          return StringUtils.lines(
-              // Itf#info has side effects due to the missing Base2.
-              "[Sub1, info]: message00",
-              "[Base2, info]: message08",
-              // Base2#info also has side effects.
-              "[Base2, info]: message4",
-              "The end"
-          );
+          return enableHorizontalClassMerging
+              ? StringUtils.lines(
+                  "[Base2, info]: message08",
+                  // Base2#info also has side effects.
+                  "[Base2, info]: message4",
+                  "The end")
+              : StringUtils.lines(
+                  // Itf#info has side effects due to the missing Base2.
+                  "[Sub1, info]: message00",
+                  "[Base2, info]: message08",
+                  // Base2#info also has side effects.
+                  "[Base2, info]: message4",
+                  "The end");
         case NON_SPECIFIC_RULES_PARTIAL:
-          return StringUtils.lines(
-              // TODO(b/133208961): Introduce comparison/meet of assume rules.
-              // Itf has side effects for all methods, since we don't compute the meet yet.
-              "[Sub1, info]: message00",
-              "[Base1, debug]: message00",
-              "[Sub1, verbose]: message00",
-              "[Base2, info]: message08",
-              "[AnotherSub2, debug]: message08",
-              "[AnotherSub2, verbose]: message08",
-              // Base2#debug also has side effects.
-              "[AnotherSub2, debug]: message5",
-              "The end"
-          );
+          return enableHorizontalClassMerging
+              ? StringUtils.lines(
+                  "[AnotherSub2, debug]: message08", "[AnotherSub2, debug]: message5", "The end")
+              : StringUtils.lines(
+                  // TODO(b/133208961): Introduce comparison/meet of assume rules.
+                  // Itf has side effects for all methods, since we don't compute the meet yet.
+                  "[Sub1, info]: message00",
+                  "[Base1, debug]: message00",
+                  "[Sub1, verbose]: message00",
+                  "[Base2, info]: message08",
+                  "[AnotherSub2, debug]: message08",
+                  "[AnotherSub2, verbose]: message08",
+                  // Base2#debug also has side effects.
+                  "[AnotherSub2, debug]: message5",
+                  "The end");
         case NON_SPECIFIC_RULES_ALL:
-          return OUTPUT_WITHOUT_MESSAGES;
+          return StringUtils.lines("The end");
         default:
           throw new Unreachable();
       }
@@ -119,38 +109,60 @@
 
   private final TestParameters parameters;
   private final TestConfig config;
+  private final boolean enableHorizontalClassMerging;
 
   @Parameterized.Parameters(name = "{0} {1}")
   public static Collection<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        TestConfig.values(),
+        BooleanUtils.values());
   }
 
-  public AssumenosideeffectsPropagationTest(TestParameters parameters, TestConfig config) {
+  public AssumenosideeffectsPropagationTest(
+      TestParameters parameters, TestConfig config, boolean enableHorizontalClassMerging) {
     this.parameters = parameters;
     this.config = config;
+    this.enableHorizontalClassMerging = enableHorizontalClassMerging;
   }
 
   @Test
   public void testJVMOutput() throws Exception {
-    assumeTrue(parameters.isCfRuntime() && config == TestConfig.SPECIFIC_RULES);
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(config == TestConfig.SPECIFIC_RULES);
+    assumeFalse(enableHorizontalClassMerging);
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutput(JVM_OUTPUT);
+        .assertSuccessWithOutputLines(
+            "[Sub1, info]: message00",
+            "[Base1, debug]: message00",
+            "[Sub1, verbose]: message00",
+            "[Sub1, info]: message1",
+            "[Base1, debug]: message2",
+            "[Sub1, verbose]: message3",
+            "[Base2, info]: message08",
+            "[AnotherSub2, debug]: message08",
+            "[AnotherSub2, verbose]: message08",
+            "[Base2, info]: message4",
+            "[AnotherSub2, debug]: message5",
+            "[AnotherSub2, verbose]: message6",
+            "The end");
   }
 
   @Test
   public void testR8() throws Exception {
-    expectThrowsWithHorizontalClassMergingIf(config != TestConfig.NON_SPECIFIC_RULES_ALL);
     testForR8(parameters.getBackend())
         .addInnerClasses(AssumenosideeffectsPropagationTest.class)
         .addKeepMainRule(MAIN)
         .addKeepRules(config.getKeepRules())
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
         .enableInliningAnnotations()
         .noMinification()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutput(config.expectedOutput());
+        .assertSuccessWithOutput(config.expectedOutput(enableHorizontalClassMerging));
   }
 
   interface Itf {
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
new file mode 100644
index 0000000..b441e6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -0,0 +1,184 @@
+// 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.shaking.attributes;
+
+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.assertNull;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+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.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MemberSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@SuppressWarnings("ALL")
+@RunWith(Parameterized.class)
+public class KeepSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String[] EXPECTED = new String[] {"Hello", "World", "Hello", "World"};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(transformer(KeptClass.class).removeInnerClasses().transform())
+        .addProgramClassFileData(transformer(NotKeptClass.class).removeInnerClasses().transform())
+        .run(parameters.getRuntime(), KeptClass.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testKeptClassFieldAndMethodFull() throws Exception {
+    // TODO(b/170077516): The below should pass in false.
+    runTest(testForR8(parameters.getBackend()), true);
+  }
+
+  @Test
+  public void testKeptClassFieldAndMethodCompat() throws Exception {
+    runTest(testForR8Compat(parameters.getBackend()), true);
+  }
+
+  private void runTest(R8TestBuilder<?> testBuilder, boolean keptForNotKept) throws Exception {
+    testBuilder
+        .addProgramClassFileData(transformer(KeptClass.class).removeInnerClasses().transform())
+        .addProgramClassFileData(transformer(NotKeptClass.class).removeInnerClasses().transform())
+        .addKeepMainRule(KeptClass.class)
+        .addKeepRules(
+            StringUtils.lines(
+                "-keep class " + KeptClass.class.getTypeName() + " {",
+                "  *** keptMethod(...);",
+                "  *** keptField;",
+                "}"))
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.INNER_CLASSES,
+            ProguardKeepAttributes.ENCLOSING_METHOD)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), KeptClass.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(inspector -> inspect(inspector, keptForNotKept));
+  }
+
+  private void inspect(CodeInspector inspector, boolean keepForNotKept) {
+    ClassSubject keptClass = inspector.clazz(KeptClass.class);
+    assertThat(keptClass, isPresent());
+    assertEquals(
+        "<T:Ljava/lang/Object;>Ljava/lang/Object;", keptClass.getFinalSignatureAttribute());
+    FieldSubject keptField = keptClass.uniqueFieldWithName("keptField");
+    assertThat(keptField, isPresent());
+    assertEquals("TT;", keptField.getFinalSignatureAttribute());
+    MethodSubject keptMethod = keptClass.uniqueMethodWithName("keptMethod");
+    assertThat(keptMethod, isPresent());
+    assertEquals("<R:Ljava/lang/Object;>(TT;)TR;", keptMethod.getFinalSignatureAttribute());
+
+    // For all remaining classes and members, we should only keep signatures if in compat mode.
+    checkMemberSignature(keptClass.uniqueFieldWithName("notKeptField"), keepForNotKept, "TT;");
+    checkMemberSignature(
+        keptClass.uniqueMethodWithName("notKeptMethod"),
+        keepForNotKept,
+        "<R:Ljava/lang/Object;>(TT;)TR;");
+
+    ClassSubject notKeptClass = inspector.clazz(NotKeptClass.class);
+    assertThat(notKeptClass, isPresent());
+    if (keepForNotKept) {
+      assertEquals(
+          "<P:Ljava/lang/Object;>Ljava/lang/Object;", notKeptClass.getFinalSignatureAttribute());
+    } else {
+      assertNull(notKeptClass.getFinalSignatureAttribute());
+    }
+    checkMemberSignature(
+        notKeptClass.uniqueFieldWithName("notKeptField"), keepForNotKept, "Ljava/util/List<TP;>;");
+    checkMemberSignature(
+        notKeptClass.uniqueMethodWithName("notKeptMethod"),
+        keepForNotKept,
+        "(TP;TP;)Ljava/util/List<TP;>;");
+  }
+
+  private void checkMemberSignature(
+      MemberSubject member, boolean keepForNotKept, String signature) {
+    assertThat(member, isPresent());
+    if (keepForNotKept) {
+      assertEquals(signature, member.getFinalSignatureAttribute());
+    } else {
+      assertNull(member.getFinalSignatureAttribute());
+    }
+  }
+
+  @NeverClassInline
+  public static class NotKeptClass<P> {
+
+    public List<P> notKeptField;
+
+    @NeverInline
+    public List<P> notKeptMethod(P p1, P p2) {
+      if (notKeptField != null) {
+        return notKeptField;
+      }
+      ArrayList<P> ps = new ArrayList<>();
+      ps.add(p1);
+      ps.add(p2);
+      notKeptField = ps;
+      return ps;
+    }
+  }
+
+  public static class KeptClass<T> {
+
+    private T keptField;
+    private T notKeptField;
+
+    public <R> R keptMethod(T t) {
+      if (keptField == null) {
+        keptField = t;
+      }
+      return (R) keptField;
+    }
+
+    @NeverInline
+    public <R> R notKeptMethod(T t) {
+      if (notKeptField == null) {
+        notKeptField = t;
+      }
+      return (R) notKeptField;
+    }
+
+    public static void main(String[] args) {
+      KeptClass<String> keptClass = new KeptClass<>();
+      System.out.println(keptClass.<String>keptMethod("Hello"));
+      System.out.println(keptClass.<String>notKeptMethod("World"));
+      NotKeptClass<String> stringNotKeptClass = new NotKeptClass<>();
+      List<String> strings = stringNotKeptClass.notKeptMethod("Hello", "World");
+      System.out.println(strings.get(0));
+      System.out.println(strings.get(1));
+    }
+  }
+}
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 367bfbc..94bdbf1 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
@@ -37,15 +37,12 @@
     return getTestParameters().withNoneRuntime().build();
   }
 
-  private final TestParameters parameters;
-
   public WhyAreYouKeepingAllTest(TestParameters parameters) {
-    this.parameters = parameters;
+    parameters.assertNoneRuntime();
   }
 
   @Test
   public void test() throws Throwable {
-    expectThrowsWithHorizontalClassMerging();
     testForR8(Backend.CF)
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
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 269c6c2..764c1bb 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.ClassAccessFlags;
@@ -241,6 +242,10 @@
   }
 
   public ClassFileTransformer setVersion(int newVersion) {
+    return setVersion(CfVersion.fromRaw(newVersion));
+  }
+
+  public ClassFileTransformer setVersion(CfVersion newVersion) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
@@ -251,7 +256,7 @@
               String signature,
               String superName,
               String[] interfaces) {
-            super.visit(newVersion, access, name, signature, superName, interfaces);
+            super.visit(newVersion.raw(), access, name, signature, superName, interfaces);
           }
         });
   }
@@ -483,11 +488,24 @@
   @FunctionalInterface
   public interface MethodPredicate {
     boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
+
+    static MethodPredicate onName(String name) {
+      return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName);
+    }
   }
 
   @FunctionalInterface
   public interface FieldPredicate {
     boolean test(int access, String name, String descriptor, String signature, Object value);
+
+    static FieldPredicate onNameAndSignature(String name, String descriptor) {
+      return (access, otherName, otherDescriptor, signature, value) ->
+          name.equals(otherName) && descriptor.equals(otherDescriptor);
+    }
+
+    static FieldPredicate onName(String name) {
+      return (access, otherName, descriptor, signature, value) -> name.equals(otherName);
+    }
   }
 
   @FunctionalInterface
@@ -518,14 +536,29 @@
         });
   }
 
-  public ClassFileTransformer renameMethod(String oldName, String newName) {
+  public ClassFileTransformer renameMethod(MethodPredicate predicate, String newName) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
           public MethodVisitor visitMethod(
               int access, String name, String descriptor, String signature, String[] exceptions) {
-            return super.visitMethod(
-                access, name.equals(oldName) ? newName : name, descriptor, signature, exceptions);
+            if (predicate.test(access, name, descriptor, signature, exceptions)) {
+              return super.visitMethod(access, newName, descriptor, signature, exceptions);
+            }
+            return super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
+  public ClassFileTransformer setGenericSignature(MethodPredicate predicate, String newSignature) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            return predicate.test(access, name, descriptor, signature, exceptions)
+                ? super.visitMethod(access, name, descriptor, newSignature, exceptions)
+                : super.visitMethod(access, name, descriptor, signature, exceptions);
           }
         });
   }
@@ -558,13 +591,13 @@
         });
   }
 
-  public ClassFileTransformer renameField(FieldSignaturePredicate predicate, String newName) {
+  public ClassFileTransformer renameField(FieldPredicate predicate, String newName) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
           public FieldVisitor visitField(
               int access, String name, String descriptor, String signature, Object value) {
-            if (predicate.test(name, descriptor)) {
+            if (predicate.test(access, name, descriptor, signature, value)) {
               return super.visitField(access, newName, descriptor, signature, value);
             } else {
               return super.visitField(access, name, descriptor, signature, value);
@@ -576,7 +609,21 @@
   public ClassFileTransformer renameAndRemapField(String oldName, String newName) {
     FieldSignaturePredicate matchPredicate = (name, signature) -> oldName.equals(name);
     remapField(matchPredicate, newName);
-    return renameField(matchPredicate, newName);
+    return renameField(FieldPredicate.onName(oldName), newName);
+  }
+
+  public ClassFileTransformer setGenericSignature(FieldPredicate predicate, String newSignature) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            if (predicate.test(access, name, descriptor, signature, value)) {
+              return super.visitField(access, name, descriptor, newSignature, value);
+            }
+            return super.visitField(access, name, descriptor, signature, value);
+          }
+        });
   }
 
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 6160dc6..ee4fe7b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public boolean isFinal() {
+    throw new Unreachable("Cannot determine if an absent class is final");
+  }
+
+  @Override
   public boolean isPresent() {
     return false;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
new file mode 100644
index 0000000..14666f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
@@ -0,0 +1,10 @@
+// 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.codeinspector;
+
+public abstract class ClassOrMemberSubject extends Subject {
+
+  public abstract boolean isFinal();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 60203c1..54e3d63 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -27,7 +27,7 @@
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 import org.junit.rules.TemporaryFolder;
 
-public abstract class ClassSubject extends Subject {
+public abstract class ClassSubject extends ClassOrMemberSubject {
 
   protected final ClassReference reference;
   protected final CodeInspector codeInspector;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index 2fd8d37..ef44c64 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -25,6 +25,7 @@
 
   public abstract String getOriginalSignatureAttribute();
 
+  @Override
   public abstract String getFinalSignatureAttribute();
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 3192f8b..bcedc29 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -63,6 +63,11 @@
   }
 
   @Override
+  public boolean isFinal() {
+    return dexClass.isFinal();
+  }
+
+  @Override
   public boolean isPresent() {
     return true;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 1f14df7..a440523 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import static com.android.tools.r8.TestBase.toDexType;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.graph.DexItemFactory;
@@ -21,6 +23,29 @@
     this.horizontallyMergedClasses = horizontallyMergedClasses;
   }
 
+  public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) {
+    assertEquals(
+        horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from, dexItemFactory)),
+        toDexType(target, dexItemFactory));
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertNoClassesMerged() {
+    assertTrue(horizontallyMergedClasses.getSources().isEmpty());
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertClassNotMerged(Class<?> clazz) {
+    assertFalse(horizontallyMergedClasses.hasBeenMerged(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
+  public HorizontallyMergedClassesInspector assertClassNotMergedIntoDifferentType(Class<?> clazz) {
+    assertFalse(
+        horizontallyMergedClasses.hasBeenMergedIntoDifferentType(toDexType(clazz, dexItemFactory)));
+    return this;
+  }
+
   public HorizontallyMergedClassesInspector assertMerged(Class<?> clazz) {
     assertTrue(
         horizontallyMergedClasses.hasBeenMergedOrIsMergeTarget(toDexType(clazz, dexItemFactory)));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 513de02..6f6bbf8 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.retrace.RetraceApi;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceMethodResult;
 
 public interface InstructionSubject {
@@ -137,11 +138,11 @@
     return retracer.retrace(methodSubject.asFoundMethodSubject().asMethodReference());
   }
 
-  default RetraceMethodResult retraceLinePosition(RetraceApi retracer) {
-    return retrace(retracer).narrowByLine(getLineNumber());
+  default RetraceFrameResult retraceLinePosition(RetraceApi retracer) {
+    return retrace(retracer).narrowByPosition(getLineNumber());
   }
 
-  default RetraceMethodResult retracePcPosition(RetraceApi retracer, MethodSubject methodSubject) {
-    return retrace(retracer).narrowByLine(getOffset(methodSubject).offset);
+  default RetraceFrameResult retracePcPosition(RetraceApi retracer, MethodSubject methodSubject) {
+    return retrace(retracer).narrowByPosition(getOffset(methodSubject).offset);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 017a637..98c8562 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -6,6 +6,7 @@
 
 import static org.hamcrest.CoreMatchers.not;
 
+import com.android.tools.r8.Collectors;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexClass;
@@ -14,14 +15,11 @@
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
 import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.retrace.RetraceMethodResult;
-import com.android.tools.r8.retrace.RetraceMethodResult.Element;
-import com.android.tools.r8.retrace.RetracedMethod;
+import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.Visibility;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
-import java.util.stream.Collectors;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -275,24 +273,21 @@
     };
   }
 
-  public static Matcher<MethodSubject> isFinal() {
-    return new TypeSafeMatcher<MethodSubject>() {
+  public static Matcher<ClassOrMemberSubject> isFinal() {
+    return new TypeSafeMatcher<ClassOrMemberSubject>() {
       @Override
-      public boolean matchesSafely(final MethodSubject method) {
-        return method.isPresent() && method.isFinal();
+      public boolean matchesSafely(ClassOrMemberSubject subject) {
+        return subject.isPresent() && subject.isFinal();
       }
 
       @Override
-      public void describeTo(final Description description) {
+      public void describeTo(Description description) {
         description.appendText("is final");
       }
 
       @Override
-      public void describeMismatchSafely(final MethodSubject method, Description description) {
-        description
-            .appendText("method ")
-            .appendValue(method.getOriginalName())
-            .appendText(" was not");
+      public void describeMismatchSafely(ClassOrMemberSubject subject, Description description) {
+        description.appendText("subject ").appendValue(subject.toString()).appendText(" was not");
       }
     };
   }
@@ -483,11 +478,12 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isInlineFrame() {
-    return new TypeSafeMatcher<RetraceMethodResult>() {
+  public static Matcher<RetraceFrameResult> isInlineFrame() {
+    return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
-      protected boolean matchesSafely(RetraceMethodResult item) {
-        return !item.isAmbiguous() && item.stream().count() > 1;
+      protected boolean matchesSafely(RetraceFrameResult item) {
+        return !item.isAmbiguous()
+            && item.stream().mapToLong(method -> method.getOuterFrames().size()).sum() > 0;
       }
 
       @Override
@@ -497,28 +493,29 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isInlineStack(LinePosition startPosition) {
-    return new TypeSafeMatcher<RetraceMethodResult>() {
+  public static Matcher<RetraceFrameResult> isInlineStack(LinePosition startPosition) {
+    return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
-      protected boolean matchesSafely(RetraceMethodResult item) {
+      protected boolean matchesSafely(RetraceFrameResult item) {
+        RetraceFrameResult.Element single = item.stream().collect(Collectors.toSingle());
         Box<LinePosition> currentPosition = new Box<>(startPosition);
         Box<Boolean> returnValue = new Box<>();
-        item.forEach(
-            element -> {
+        single.visitFrames(
+            (method, __) -> {
               boolean sameMethod;
               LinePosition currentInline = currentPosition.get();
               if (currentInline == null) {
                 returnValue.set(false);
                 return;
               }
+              if (method.isUnknown()) {
+                returnValue.set(false);
+                return;
+              }
               sameMethod =
-                  element.getMethod().isKnown()
-                      && element
-                          .getMethod()
-                          .asKnown()
-                          .equalsMethodReference(currentInline.methodReference);
+                  method.asKnown().getMethodReference().equals(currentInline.methodReference);
               boolean samePosition =
-                  element.getOriginalLineNumber(currentInline.minifiedPosition)
+                  method.getOriginalPositionOrDefault(currentInline.minifiedPosition)
                       == currentInline.originalPosition;
               if (!returnValue.isSet() || returnValue.get()) {
                 returnValue.set(sameMethod & samePosition);
@@ -535,28 +532,24 @@
     };
   }
 
-  public static Matcher<RetraceMethodResult> isTopOfStackTrace(
+  public static Matcher<RetraceFrameResult> isTopOfStackTrace(
       StackTrace stackTrace, List<Integer> minifiedPositions) {
-    return new TypeSafeMatcher<RetraceMethodResult>() {
+    return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
-      protected boolean matchesSafely(RetraceMethodResult item) {
-        List<Element> retraceElements = item.stream().collect(Collectors.toList());
-        if (retraceElements.size() > stackTrace.size()
-            || retraceElements.size() != minifiedPositions.size()) {
-          return false;
-        }
-        for (int i = 0; i < retraceElements.size(); i++) {
-          Element retraceElement = retraceElements.get(i);
-          StackTraceLine stackTraceLine = stackTrace.get(i);
-          RetracedMethod methodReference = retraceElement.getMethod();
-          if (!stackTraceLine.methodName.equals(methodReference.getMethodName())
-              || !stackTraceLine.className.equals(methodReference.getHolderClass().getTypeName())
-              || stackTraceLine.lineNumber
-                  != retraceElement.getOriginalLineNumber(minifiedPositions.get(i))) {
-            return false;
-          }
-        }
-        return true;
+      protected boolean matchesSafely(RetraceFrameResult item) {
+        RetraceFrameResult.Element single = item.stream().collect(Collectors.toSingle());
+        Box<Boolean> matches = new Box<>(true);
+        single.visitFrames(
+            (method, index) -> {
+              StackTraceLine stackTraceLine = stackTrace.get(index);
+              if (!stackTraceLine.methodName.equals(method.getMethodName())
+                  || !stackTraceLine.className.equals(method.getHolderClass().getTypeName())
+                  || stackTraceLine.lineNumber
+                      != method.getOriginalPositionOrDefault(minifiedPositions.get(index))) {
+                matches.set(false);
+              }
+            });
+        return matches.get();
       }
 
       @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index 77172c3..847675f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.naming.MemberNaming.Signature;
 
-public abstract class MemberSubject extends Subject {
+public abstract class MemberSubject extends ClassOrMemberSubject {
 
   public abstract boolean isPublic();
 
@@ -18,8 +18,6 @@
 
   public abstract boolean isStatic();
 
-  public abstract boolean isFinal();
-
   public abstract Signature getOriginalSignature();
 
   public abstract Signature getFinalSignature();
@@ -48,6 +46,8 @@
     return finalSignature == null ? null : finalSignature.name;
   }
 
+  public abstract String getFinalSignatureAttribute();
+
   public abstract AnnotationSubject annotation(String name);
 
   public FieldSubject asFieldSubject() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 3c86361..c6a63e2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -42,6 +42,7 @@
 
   public abstract String getOriginalSignatureAttribute();
 
+  @Override
   public abstract String getFinalSignatureAttribute();
 
   public abstract DexEncodedMethod getMethod();
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 94ef0dc..00f4380 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -5,25 +5,24 @@
 
 import apk_masseur
 import apk_utils
+import as_utils
 import golem
-import gradle
 import jdk
 import json
-import os
 import optparse
+import os
 import shutil
 import signal
 import subprocess
 import sys
 import time
-import utils
-import zipfile
-from xml.dom import minidom
-from datetime import datetime
-
-import as_utils
 import update_prebuilds_in_android
-import download_all_benchmark_dependencies
+import zipfile
+from datetime import datetime
+from xml.dom import minidom
+
+import gradle
+import utils
 
 SHRINKERS = ['r8', 'r8-full', 'r8-nolib', 'r8-nolib-full', 'pg']
 WORKING_DIR = os.path.join(utils.BUILD, 'opensource_apps')
@@ -62,7 +61,9 @@
       'releaseTarget': None,
       'signed_apk_name': None,
       'skip': False,
-      'has_lint_task': True
+      'has_lint_task': True,
+      'legacy_desugaring': False,
+      'compatibility': None
     }
     self.__dict__ = dict(defaults.items() + fields.items())
 
@@ -114,7 +115,9 @@
               'id': 'dev.dworks.apps.anexplorer.pro',
               'flavor': 'googleMobilePro',
               'signed_apk_name': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
-              'min_sdk': 17
+              'min_sdk': 17,
+              'legacy_desugaring': True,
+              'compatibility': '1.7',
           })
       ]
   }),
@@ -161,7 +164,9 @@
       'apps': [
           App({
               'id': 'com.chanapps.four.activity',
-              'has_lint_task': False
+              'has_lint_task': False,
+              'min_sdk': 15,
+              'compatibility': '1.7',
           })
       ]
   }),
@@ -171,7 +176,9 @@
       'revision': '10091fa0ec37da12e66286559ad1b6098976b07b',
       'apps': [
           App({
-              'id': 'com.google.firebase.example.fireeats'
+              'id': 'com.google.firebase.example.fireeats',
+              'min_sdk': 16,
+              'compatibility': '1.7',
           })
       ]
   }),
@@ -194,7 +201,9 @@
       'revision': 'b8df78c96630a6537fbc07787b4990afc030cc0f',
       'apps': [
           App({
-             'id': 'com.example.instabug'
+             'id': 'com.example.instabug',
+             'min_sdk': 15,
+             'compatibility': '1.7',
           })
       ]
   }),
@@ -218,7 +227,9 @@
       'revision': '093da9ee0512e67192f62951c45a07a616fc3224',
       'apps': [
           App({
-              'id': 'fr.neamar.kiss'
+              'id': 'fr.neamar.kiss',
+              'min_sdk': 15,
+              'compatibility': '1.7',
           })
       ]
   }),
@@ -374,8 +385,9 @@
           App({
               'id': 'eu.kanade.tachiyomi',
               'flavor': 'dev',
-              'min_sdk': 16,
-              'has_lint_task': False
+              'min_sdk': 21,
+              'has_lint_task': False,
+              'compatibility': '1.7',
           })
       ]
   }),
@@ -970,7 +982,10 @@
          '--pg-conf', proguard_config_file,
          '--lib', android_jar,
          '--output', zip_dest,
-         apk])
+         apk] +
+         (['--no-desugaring']
+         if app.legacy_desugaring or app.compatibility == '1.7'
+         else []))
 
   for android_optional_jar in utils.get_android_optional_jars(compile_sdk):
     cmd.append('--lib')