Merge commit '34e007329bb88f14c7112229456dec53e2f724d9' into dev-release
diff --git a/.gitignore b/.gitignore
index ddab9db..e64324a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -153,6 +153,8 @@
 tools/*/art-8.1.0.tar.gz
 tools/*/art-9.0.0
 tools/*/art-9.0.0.tar.gz
+tools/*/art-10.0.0
+tools/*/art-10.0.0.tar.gz
 tools/*/art.tar.gz
 tools/*/dalvik
 tools/*/dalvik-4.0.4
diff --git a/build.gradle b/build.gradle
index 2e64458..8f323aa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -332,6 +332,7 @@
                 "linux/art-7.0.0",
                 "linux/art-8.1.0",
                 "linux/art-9.0.0",
+                "linux/art-10.0.0",
                 "linux/dalvik",
                 "linux/dalvik-4.0.4",
                 "${osString}/dx",
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 75398ad..87562ad 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -281,6 +281,24 @@
       }
     }
     builders {
+      name: "linux-android-10.0.0"
+      mixins: "linux"
+      mixins: "normal"
+      recipe {
+        properties: "tool:r8"
+        properties: "dex_vm:10.0.0"
+      }
+    }
+    builders {
+      name: "linux-android-10.0.0_release"
+      mixins: "linux"
+      mixins: "normal"
+      recipe {
+        properties: "tool:r8"
+        properties: "dex_vm:10.0.0"
+      }
+    }
+    builders {
       name: "linux-internal"
       mixins: "linux"
       mixins: "internal"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 7bdd5c2..faf51f3 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -51,6 +51,11 @@
     short_name: "9.0.0"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-android-10.0.0"
+    category: "R8"
+    short_name: "10.0.0"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-internal"
     category: "R8"
     short_name: "internal"
@@ -131,6 +136,11 @@
     short_name: "9.0.0"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-android-10.0.0_release"
+    category: "R8 release"
+    short_name: "10.0.0"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-internal_release"
     category: "R8 release"
     short_name: "internal"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index bcd03ba..5e37d53 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -104,6 +104,16 @@
     repository: "https://r8.googlesource.com/r8"
   }
   builders {
+    name: "linux-android-10.0.0"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
+    name: "linux-android-10.0.0_release"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
     name: "linux-internal"
     bucket: "ci"
     repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index dd8fdfa..dd2df27 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -34,6 +34,7 @@
   triggers: "linux-android-7.0.0"
   triggers: "linux-android-8.1.0"
   triggers: "linux-android-9.0.0"
+  triggers: "linux-android-10.0.0"
   triggers: "linux-run-on-as-app"
   triggers: "linux-run-on-as-app-recompilation"
   triggers: "linux-internal"
@@ -77,6 +78,17 @@
   triggers: "windows_release"
 }
 
+trigger {
+  id: "branch-gitiles-trigger-10"
+  acl_sets: "default"
+  gitiles: {
+    repo: "https://r8.googlesource.com/r8"
+    refs: "refs/heads/2.0"
+    path_regexps: "src/main/java/com/android/tools/r8/Version.java"
+  }
+  triggers: "linux-android-10.0.0_release"
+}
+
 
 job {
   id: "archive"
@@ -301,6 +313,32 @@
 }
 
 job {
+  id: "linux-android-10.0.0"
+  acl_sets: "default"
+  triggering_policy: {
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-10.0.0"
+  }
+}
+
+job {
+  id: "linux-android-10.0.0_release"
+  acl_sets: "default"
+  triggering_policy: {
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-android-10.0.0_release"
+  }
+}
+
+job {
   id: "linux-internal"
   acl_sets: "default"
   buildbucket {
diff --git a/scripts/update-host-art.sh b/scripts/update-host-art.sh
index fb45744..76572e0 100755
--- a/scripts/update-host-art.sh
+++ b/scripts/update-host-art.sh
@@ -109,7 +109,10 @@
 # dalvikvm32 or dalvikvm64).
 cp $ANDROID_HOST_BUILD/bin/dalvikvm $DEST/bin
 cp $ANDROID_HOST_BUILD/bin/dex2oat $DEST/bin
-cp $ANDROID_HOST_BUILD/bin/patchoat $DEST/bin
+if [ -e $ANDROID_HOST_BUILD/bin/patchoat ]
+  # File patchoat does not exist on Q anymore.
+  then cp $ANDROID_HOST_BUILD/bin/patchoat $DEST/bin
+fi
 
 # Required framework files.
 mkdir -p $DEST/framework
@@ -127,8 +130,13 @@
 cp -r $ANDROID_TARGET_BUILD/product/$ANDROID_PRODUCT/system/framework/* $DEST/product/$ANDROID_PRODUCT/system/framework
 
 # Required auxillary files.
-mkdir -p $DEST/usr/icu
-cp -r $ANDROID_HOST_BUILD/usr/icu/* $DEST/usr/icu
+if [ -e $ANDROID_HOST_BUILD/usr/icu ]; then
+  mkdir -p $DEST/usr/icu
+  cp -r $ANDROID_HOST_BUILD/usr/icu/* $DEST/usr/icu
+else
+  mkdir -p $DEST/com.android.runtime/etc/icu/
+  cp -r $ANDROID_HOST_BUILD/com.android.runtime/etc/icu/* $DEST/com.android.runtime/etc/icu/
+fi
 
 # Update links for vdex files for Android P and later.
 if [ -f $DEST/product/$ANDROID_PRODUCT/system/framework/boot.vdex ]; then
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index dcb4f9a..28713e3 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -90,9 +90,6 @@
           });
       if (shrink) {
         InternalOptions r8Options = r8Command.getInternalOptions();
-        // TODO(b/143590191): Remove the hack and make it work by default.
-        // Temporary hack so that keeping an interface keeps the superinterfaces.
-        r8Options.testing.keepInheritedInterfaceMethods = true;
         // Disable outlining for R8 when called from L8.
         r8Options.outline.enabled = false;
         R8.runForTesting(r8Command.getInputApp(), r8Options);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index d96a5a1..646a96a 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -163,8 +163,8 @@
     }
 
     public boolean isShrinking() {
-      // Answers true if keep rules, even empty, are provided.
-      return !proguardConfigStrings.isEmpty() || !proguardConfigFiles.isEmpty();
+       // Disable for release.
+       return false;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/OptionalBool.java b/src/main/java/com/android/tools/r8/OptionalBool.java
deleted file mode 100644
index 92f5d07..0000000
--- a/src/main/java/com/android/tools/r8/OptionalBool.java
+++ /dev/null
@@ -1,84 +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;
-
-/** Three point boolean lattice. */
-public abstract class OptionalBool {
-
-  private static final OptionalBool TRUE =
-      new OptionalBool() {
-        @Override
-        public boolean isTrue() {
-          return true;
-        }
-
-        @Override
-        public String toString() {
-          return "true";
-        }
-      };
-
-  private static final OptionalBool FALSE =
-      new OptionalBool() {
-        @Override
-        public boolean isFalse() {
-          return true;
-        }
-
-        @Override
-        public String toString() {
-          return "false";
-        }
-      };
-
-  private static final OptionalBool UNKNOWN =
-      new OptionalBool() {
-        @Override
-        public boolean isUnknown() {
-          return true;
-        }
-
-        @Override
-        public String toString() {
-          return "unknown";
-        }
-      };
-
-  public static OptionalBool of(boolean bool) {
-    return bool ? TRUE : FALSE;
-  }
-
-  public static OptionalBool unknown() {
-    return UNKNOWN;
-  }
-
-  private OptionalBool() {}
-
-  public boolean isTrue() {
-    return false;
-  }
-
-  public boolean isFalse() {
-    return false;
-  }
-
-  public boolean isUnknown() {
-    return false;
-  }
-
-  public boolean isPossiblyTrue() {
-    return !isFalse();
-  }
-
-  public boolean isPossiblyFalse() {
-    return !isTrue();
-  }
-
-  public boolean getBooleanValue() {
-    if (isUnknown()) {
-      throw new IllegalStateException("Attempt to convert unknown value to a boolean");
-    }
-    return isTrue();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 3246478..0d8f3ba 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -246,7 +246,8 @@
       clazz.forEachMethod(
           method -> {
             ResolutionResult resolutionResult = appInfo.resolveMethod(superType, method.method);
-            for (DexEncodedMethod dexEncodedMethod : resolutionResult.asListOfTargets()) {
+            DexEncodedMethod dexEncodedMethod = resolutionResult.getSingleTarget();
+            if (dexEncodedMethod != null) {
               addMethod(dexEncodedMethod.method);
             }
           });
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6db32eb..ab6796b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -868,6 +868,9 @@
   }
 
   private void computeKotlinInfoForProgramClasses(DexApplication application, AppView<?> appView) {
+    if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+      return;
+    }
     Kotlin kotlin = appView.dexItemFactory().kotlin;
     Reporter reporter = options.reporter;
     for (DexProgramClass programClass : application.classes()) {
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 fd0c968..afdc15b 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
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -158,26 +160,10 @@
           if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
             type = Type.DIRECT;
           } else if (code.getOriginalHolder() == method.holder) {
-            if (!this.itf || builder.appView.options().isInterfaceMethodDesugaringEnabled()) {
-              // When desugaring default interface methods, it is expected they are targeted with
-              // invoke-direct.
-              type = Type.DIRECT;
-            } else {
-              DexProgramClass clazz = builder.appView.definitionForProgramType(method.holder);
-              assert clazz != null;
-              DexEncodedMethod encodedMethod = clazz.lookupDirectMethod(method);
-              if (encodedMethod != null) {
-                assert encodedMethod.isStatic() || encodedMethod.isPrivateMethod();
-                type = Type.DIRECT;
-              } else {
-                // This is a default interface method.
-                type = Type.SUPER;
-              }
-            }
+            type = invokeTypeForInvokeSpecialToNonInitMethodOnHolder(builder.appView, code);
           } else {
             type = Type.SUPER;
           }
-          assert type == Type.SUPER || type == Type.DIRECT;
           break;
         }
       case Opcodes.INVOKESTATIC:
@@ -216,4 +202,39 @@
     return InliningConstraintVisitor.getConstraintForInvoke(
         opcode, method, graphLense, appView, inliningConstraints, invocationContext);
   }
+
+  private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
+      AppView<?> appView, CfSourceCode code) {
+    boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
+    DexEncodedMethod encodedMethod = lookupMethod(appView, method);
+    if (encodedMethod == null) {
+      // The method is not defined on the class, we can use super to target. When desugaring
+      // default interface methods, it is expected they are targeted with invoke-direct.
+      return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
+    }
+    if (!encodedMethod.isVirtualMethod()) {
+      return Type.DIRECT;
+    }
+    if (encodedMethod.accessFlags.isFinal()) {
+      // This method is final which indicates no subtype will overwrite it, we can use
+      // invoke-virtual.
+      return Type.VIRTUAL;
+    }
+    if (this.itf && encodedMethod.isDefaultMethod()) {
+      return desugaringEnabled ? Type.DIRECT : Type.SUPER;
+    }
+    // We cannot emulate the semantics of invoke-special in this case and should throw a compilation
+    // error.
+    throw new CompilationError(
+        "Failed to compile unsupported use of invokespecial", code.getOrigin());
+  }
+
+  private DexEncodedMethod lookupMethod(AppView<?> appView, DexMethod method) {
+    GraphLenseLookupResult lookupResult =
+        appView.graphLense().lookupMethod(method, method, Type.DIRECT);
+    DexMethod rewrittenMethod = lookupResult.getMethod();
+    DexProgramClass clazz = appView.definitionForProgramType(rewrittenMethod.holder);
+    assert clazz != null;
+    return clazz.lookupMethod(rewrittenMethod);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3066b4d..6eb6a94 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -180,6 +180,10 @@
     return existing != null ? existing : typeDefinitions;
   }
 
+  public void invalidateTypeCacheFor(DexType type) {
+    definitions.remove(type);
+  }
+
   /**
    * Lookup static method following the super chain from the holder of {@code method}.
    * <p>
@@ -365,25 +369,15 @@
    * Section 5.4.3.3 of the JVM Spec</a>.
    */
   private DexEncodedMethod resolveMethodOnClassStep2(DexClass clazz, DexMethod method) {
-    // Pt. 1: Signature polymorphic method check. Those are only allowed on
-    //        java.lang.invoke.MethodHandle, so we only need to look for it if we are looking at
-    //        that type.
+    // Pt. 1: Signature polymorphic method check.
     // See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
     // Section 2.9 of the JVM Spec</a>.
-    if (clazz.type == dexItemFactory.methodHandleType) {
-      DexMethod signaturePolymorphic = dexItemFactory.createMethod(clazz.type,
-          dexItemFactory.createProto(
-              dexItemFactory.objectType, dexItemFactory.objectArrayType),
-          method.name);
-      DexEncodedMethod result = clazz.lookupMethod(signaturePolymorphic);
-      // Check we found a result and that it has the required access flags for signature polymorphic
-      // functions.
-      if (result != null && result.accessFlags.isNative() && result.accessFlags.isVarargs()) {
-        return result;
-      }
+    DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(method.name, dexItemFactory);
+    if (result != null) {
+      return result;
     }
     // Pt 2: Find a method that matches the descriptor.
-    DexEncodedMethod result = clazz.lookupMethod(method);
+    result = clazz.lookupMethod(method);
     if (result != null) {
       return result;
     }
@@ -566,34 +560,6 @@
     return null;
   }
 
-  /**
-   * Implements the dispatch logic for a static invoke operation.
-   * <p>
-   * The only requirement is that the method is indeed static.
-   */
-  public DexEncodedMethod dispatchStaticInvoke(ResolutionResult resolvedMethod) {
-    assert checkIfObsolete();
-    DexEncodedMethod target = resolvedMethod.getSingleTarget();
-    if (target != null && target.accessFlags.isStatic()) {
-      return target;
-    }
-    return null;
-  }
-
-  /**
-   * Implements the dispatch logic for the direct parts of a invokespecial instruction.
-   * <p>
-   * The only requirement is that the method is not static.
-   */
-  public DexEncodedMethod dispatchDirectInvoke(ResolutionResult resolvedMethod) {
-    assert checkIfObsolete();
-    DexEncodedMethod target = resolvedMethod.getSingleTarget();
-    if (target != null && !target.accessFlags.isStatic()) {
-      return target;
-    }
-    return null;
-  }
-
   public boolean hasSubtyping() {
     assert checkIfObsolete();
     return false;
@@ -670,7 +636,7 @@
         return NoSuchMethodResult.INSTANCE;
       }
       // Fast path in the common case of a single method.
-      if (false && maximallySpecificMethods.size() == 1) {
+      if (maximallySpecificMethods.size() == 1) {
         return new SingleResolutionResult(maximallySpecificMethods.values().iterator().next());
       }
       DexEncodedMethod firstMaximallySpecificMethod = null;
@@ -696,7 +662,7 @@
       if (nonAbstractMethods.size() == 1) {
         return new SingleResolutionResult(nonAbstractMethods.get(0));
       }
-      return new IncompatibleClassResult(Collections.emptyList(), nonAbstractMethods);
+      return IncompatibleClassResult.create(nonAbstractMethods);
     }
   }
 
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 90cc0f4..a2d6967 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.OptionalBool;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis.InitializedClassesInInstanceMethods;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
@@ -16,6 +16,7 @@
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
 import java.util.Set;
@@ -240,6 +241,14 @@
     return defaultValue;
   }
 
+  public <U> U withGeneratedMessageLiteBuilderShrinker(
+      Function<GeneratedMessageLiteBuilderShrinker, U> fn, U defaultValue) {
+    if (protoShrinker != null && protoShrinker.generatedMessageLiteBuilderShrinker != null) {
+      return fn.apply(protoShrinker.generatedMessageLiteBuilderShrinker);
+    }
+    return defaultValue;
+  }
+
   public GraphLense graphLense() {
     return graphLense;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 0acef70..72e372a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -3,13 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.OptionalBool;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.PredicateUtils;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
@@ -581,6 +581,38 @@
     return result == null ? lookupVirtualMethod(method) : result;
   }
 
+  public DexEncodedMethod lookupSignaturePolymorphicMethod(
+      DexString methodName, DexItemFactory factory) {
+    if (type != factory.methodHandleType && type != factory.varHandleType) {
+      return null;
+    }
+    DexEncodedMethod matchingName = null;
+    DexEncodedMethod signaturePolymorphicMethod = null;
+    for (DexEncodedMethod method : virtualMethods) {
+      if (method.method.name == methodName) {
+        if (matchingName != null) {
+          // The jvm spec, section 5.4.3.3 details that there must be exactly one method with the
+          // given name only.
+          return null;
+        }
+        matchingName = method;
+        if (isSignaturePolymorphicMethod(method, factory)) {
+          signaturePolymorphicMethod = method;
+        }
+      }
+    }
+    return signaturePolymorphicMethod;
+  }
+
+  private boolean isSignaturePolymorphicMethod(DexEncodedMethod method, DexItemFactory factory) {
+    assert method.method.holder == factory.methodHandleType
+        || method.method.holder == factory.varHandleType;
+    return method.accessFlags.isVarargs()
+        && method.accessFlags.isNative()
+        && method.method.proto.parameters.size() == 1
+        && method.method.proto.parameters.values[0] != factory.objectArrayType;
+  }
+
   private <T extends DexItem, S extends Descriptor<T, S>> T lookupTarget(T[] items, S descriptor) {
     for (T entry : items) {
       if (descriptor.match(entry)) {
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 415e65b..47930c2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,7 +10,6 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 
-import com.android.tools.r8.OptionalBool;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfInstruction;
@@ -62,6 +61,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index acc5942..ec2f8e3 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -4,11 +4,10 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -22,17 +21,10 @@
     return null;
   }
 
-  // TODO(b/140214802): Remove this method as its usage is questionable.
-  public abstract DexEncodedMethod asResultOfResolve();
-
   public abstract DexEncodedMethod getSingleTarget();
 
   public abstract boolean hasSingleTarget();
 
-  public abstract List<DexEncodedMethod> asListOfTargets();
-
-  public abstract void forEachTarget(Consumer<DexEncodedMethod> consumer);
-
   public abstract boolean isValidVirtualTarget(InternalOptions options);
 
   public abstract boolean isValidVirtualTargetForDynamicDispatch();
@@ -46,10 +38,9 @@
   public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
     assert isValidVirtualTarget(appInfo.app().options);
     // First add the target for receiver type method.type.
-    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
-    forEachTarget(result::add);
+    DexEncodedMethod encodedMethod = getSingleTarget();
+    Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
     // Add all matching targets from the subclass hierarchy.
-    DexEncodedMethod encodedMethod = asResultOfResolve();
     DexMethod method = encodedMethod.method;
     // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
     //   receiver type if available.
@@ -57,12 +48,10 @@
       DexClass clazz = appInfo.definitionFor(type);
       if (!clazz.isInterface()) {
         ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
-        methods.forEachTarget(
-            target -> {
-              if (target.isVirtualMethod()) {
-                result.add(target);
-              }
-            });
+        DexEncodedMethod target = methods.getSingleTarget();
+        if (target != null && target.isVirtualMethod()) {
+          result.add(target);
+        }
       }
     }
     return result;
@@ -102,7 +91,7 @@
       }
     }
 
-    DexEncodedMethod encodedMethod = asResultOfResolve();
+    DexEncodedMethod encodedMethod = getSingleTarget();
     DexMethod method = encodedMethod.method;
     Consumer<DexEncodedMethod> addIfNotAbstract =
         m -> {
@@ -128,10 +117,14 @@
       DexClass clazz = appInfo.definitionFor(type);
       if (clazz.isInterface()) {
         ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
-        targetMethods.forEachTarget(addIfNotAbstractAndBridge);
+        if (targetMethods.hasSingleTarget()) {
+          addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
+        }
       } else {
         ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
-        targetMethods.forEachTarget(addIfNotAbstract);
+        if (targetMethods.hasSingleTarget()) {
+          addIfNotAbstract.accept(targetMethods.getSingleTarget());
+        }
       }
     }
     return result;
@@ -162,11 +155,6 @@
     }
 
     @Override
-    public DexEncodedMethod asResultOfResolve() {
-      return resolutionTarget;
-    }
-
-    @Override
     public DexEncodedMethod getSingleTarget() {
       return resolutionTarget;
     }
@@ -175,83 +163,11 @@
     public boolean hasSingleTarget() {
       return true;
     }
-
-    @Override
-    public List<DexEncodedMethod> asListOfTargets() {
-      return Collections.singletonList(resolutionTarget);
-    }
-
-    @Override
-    public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
-      consumer.accept(resolutionTarget);
-    }
-  }
-
-  public static class MultiResolutionResult extends ResolutionResult {
-
-    private final ImmutableList<DexEncodedMethod> methods;
-
-    public MultiResolutionResult(ImmutableList<DexEncodedMethod> results) {
-      assert results.size() > 1;
-      this.methods = results;
-    }
-
-    @Override
-    public boolean isValidVirtualTarget(InternalOptions options) {
-      for (DexEncodedMethod method : methods) {
-        if (!SingleResolutionResult.isValidVirtualTarget(options, method)) {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    @Override
-    public boolean isValidVirtualTargetForDynamicDispatch() {
-      for (DexEncodedMethod method : methods) {
-        if (!method.isVirtualMethod()) {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    @Override
-    public DexEncodedMethod asResultOfResolve() {
-      // Resolution may return any of the targets that were found.
-      return methods.get(0);
-    }
-
-    @Override
-    public DexEncodedMethod getSingleTarget() {
-      // There is no single target that is guaranteed to be called.
-      return null;
-    }
-
-    @Override
-    public boolean hasSingleTarget() {
-      return false;
-    }
-
-    @Override
-    public List<DexEncodedMethod> asListOfTargets() {
-      return methods;
-    }
-
-    @Override
-    public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
-      methods.forEach(consumer);
-    }
   }
 
   public abstract static class EmptyResult extends ResolutionResult {
 
     @Override
-    public DexEncodedMethod asResultOfResolve() {
-      return null;
-    }
-
-    @Override
     public DexEncodedMethod getSingleTarget() {
       return null;
     }
@@ -262,16 +178,6 @@
     }
 
     @Override
-    public List<DexEncodedMethod> asListOfTargets() {
-      return Collections.emptyList();
-    }
-
-    @Override
-    public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
-      // Intentionally left empty.
-    }
-
-    @Override
     public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
       return null;
     }
@@ -313,9 +219,7 @@
       return this;
     }
 
-    public void forEachFailureDependency(
-        Consumer<DexProgramClass> classesCausingFailure,
-        Consumer<DexEncodedMethod> methodsCausingFailure) {
+    public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
       // Default failure has no dependencies.
     }
 
@@ -339,28 +243,24 @@
   }
 
   public static class IncompatibleClassResult extends FailedResolutionResult {
-    static final IncompatibleClassResult INSTANCE = new IncompatibleClassResult();
+    static final IncompatibleClassResult INSTANCE =
+        new IncompatibleClassResult(Collections.emptyList());
 
-    private final Collection<DexProgramClass> classesCausingError;
     private final Collection<DexEncodedMethod> methodsCausingError;
 
-    private IncompatibleClassResult() {
-      this(Collections.emptyList(), Collections.emptyList());
-    }
-
-    IncompatibleClassResult(
-        Collection<DexProgramClass> classesCausingError,
-        Collection<DexEncodedMethod> methodsCausingError) {
-      this.classesCausingError = classesCausingError;
+    private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
       this.methodsCausingError = methodsCausingError;
     }
 
+    static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
+      return methodsCausingError.isEmpty()
+          ? INSTANCE
+          : new IncompatibleClassResult(methodsCausingError);
+    }
+
     @Override
-    public void forEachFailureDependency(
-        Consumer<DexProgramClass> classesCausingFailure,
-        Consumer<DexEncodedMethod> methodsCausingFailure) {
-      this.classesCausingError.forEach(classesCausingFailure);
-      this.methodsCausingError.forEach(methodsCausingFailure);
+    public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
+      this.methodsCausingError.forEach(methodCausingFailureConsumer);
     }
   }
 
@@ -370,8 +270,5 @@
     private NoSuchMethodResult() {
       // Intentionally left empty.
     }
-
-    // TODO(b/144085169): Consider if the resolution resulting in a NoSuchMethodError should
-    // be preserved by ensuring its class is marked. Otherwise, the error may become ClassNotFound.
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 827529a..e5ace97 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.analysis;
 
-import com.android.tools.r8.OptionalBool;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -35,6 +34,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.ArrayDeque;
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 35f6661..2c86c7b 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
@@ -25,12 +25,12 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.DominatorTree.Assumption;
-import com.android.tools.r8.ir.code.FieldInstruction;
 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.NewInstance;
+import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -56,11 +56,9 @@
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
       OptimizationFeedback feedback,
-      DexProgramClass clazz,
       DexEncodedMethod method) {
-    assert clazz.type == method.method.holder;
     this.appView = appView;
-    this.clazz = clazz;
+    this.clazz = appView.definitionFor(method.method.holder).asProgramClass();
     this.code = code;
     this.feedback = feedback;
     this.method = method;
@@ -69,26 +67,11 @@
 
   public static void run(
       AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) {
-    if (!appView.enableWholeProgramOptimizations()) {
-      return;
+    if (appView.enableWholeProgramOptimizations() && method.isClassInitializer()) {
+      assert appView.appInfo().hasLiveness();
+      new FieldValueAnalysis(appView.withLiveness(), code, feedback, method)
+          .computeFieldOptimizationInfo();
     }
-    assert appView.appInfo().hasLiveness();
-    if (!method.isInitializer()) {
-      return;
-    }
-    DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
-    if (method.isInstanceInitializer()) {
-      DexEncodedMethod otherInstanceInitializer =
-          clazz.lookupDirectMethod(other -> other.isInstanceInitializer() && other != method);
-      if (otherInstanceInitializer != null) {
-        // Conservatively bail out.
-        // TODO(b/125282093): Handle multiple instance initializers on the same class.
-        return;
-      }
-    }
-
-    new FieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
-        .computeFieldOptimizationInfo();
   }
 
   private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() {
@@ -103,50 +86,54 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     DominatorTree dominatorTree = null;
 
-    DexType context = method.method.holder;
+    if (method.isClassInitializer()) {
+      DexType context = method.method.holder;
 
-    // Find all the static-put instructions that assign a field in the enclosing class which is
-    // guaranteed to be assigned only in the current initializer.
-    boolean isStraightLineCode = true;
-    Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
-    for (Instruction instruction : code.instructions()) {
-      if (instruction.isFieldPut()) {
-        FieldInstruction fieldPut = instruction.asFieldInstruction();
-        DexField field = fieldPut.getField();
-        DexEncodedField encodedField = appInfo.resolveField(field);
-        if (encodedField != null
-            && encodedField.field.holder == context
-            && appInfo.isFieldOnlyWrittenInMethod(encodedField, method)) {
-          putsPerField.computeIfAbsent(encodedField, ignore -> new LinkedList<>()).add(fieldPut);
+      // Find all the static-put instructions that assign a field in the enclosing class which is
+      // guaranteed to be assigned only in the current initializer.
+      boolean isStraightLineCode = true;
+      Map<DexEncodedField, LinkedList<StaticPut>> staticPutsPerField = new IdentityHashMap<>();
+      for (Instruction instruction : code.instructions()) {
+        if (instruction.isStaticPut()) {
+          StaticPut staticPut = instruction.asStaticPut();
+          DexField field = staticPut.getField();
+          DexEncodedField encodedField = appInfo.resolveField(field);
+          if (encodedField != null
+              && encodedField.field.holder == context
+              && appInfo.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(encodedField)) {
+            staticPutsPerField
+                .computeIfAbsent(encodedField, ignore -> new LinkedList<>())
+                .add(staticPut);
+          }
+        }
+        if (instruction.isJumpInstruction()) {
+          if (!instruction.isGoto() && !instruction.isReturn()) {
+            isStraightLineCode = false;
+          }
         }
       }
-      if (instruction.isJumpInstruction()) {
-        if (!instruction.isGoto() && !instruction.isReturn()) {
-          isStraightLineCode = false;
-        }
-      }
-    }
 
-    List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
-    for (Entry<DexEncodedField, LinkedList<FieldInstruction>> entry : putsPerField.entrySet()) {
-      DexEncodedField encodedField = entry.getKey();
-      LinkedList<FieldInstruction> fieldPuts = entry.getValue();
-      if (fieldPuts.size() > 1) {
-        continue;
-      }
-      FieldInstruction fieldPut = fieldPuts.getFirst();
-      if (!isStraightLineCode) {
-        if (dominatorTree == null) {
-          dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
-        }
-        if (!dominatorTree.dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+      List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+      for (Entry<DexEncodedField, LinkedList<StaticPut>> entry : staticPutsPerField.entrySet()) {
+        DexEncodedField encodedField = entry.getKey();
+        LinkedList<StaticPut> staticPuts = entry.getValue();
+        if (staticPuts.size() > 1) {
           continue;
         }
+        StaticPut staticPut = staticPuts.getFirst();
+        if (!isStraightLineCode) {
+          if (dominatorTree == null) {
+            dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS);
+          }
+          if (!dominatorTree.dominatesAllOf(staticPut.getBlock(), normalExitBlocks)) {
+            continue;
+          }
+        }
+        if (fieldMaybeReadBeforeInstruction(encodedField, staticPut)) {
+          continue;
+        }
+        updateFieldOptimizationInfo(encodedField, staticPut.value());
       }
-      if (fieldMaybeReadBeforeInstruction(encodedField, fieldPut)) {
-        continue;
-      }
-      updateFieldOptimizationInfo(encodedField, fieldPut.value());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
new file mode 100644
index 0000000..0e8da38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -0,0 +1,125 @@
+// 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.ir.analysis.proto;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+
+// TODO(b/112437944): Should remove the new Builder() instructions from each dynamicMethod() that
+//  references a dead proto builder.
+public class GeneratedMessageLiteBuilderShrinker {
+
+  private final ProtoReferences references;
+
+  GeneratedMessageLiteBuilderShrinker(ProtoReferences references) {
+    this.references = references;
+  }
+
+  /** Returns true if an action was deferred. */
+  public boolean deferDeadProtoBuilders(
+      DexProgramClass clazz, DexEncodedMethod context, BooleanSupplier register) {
+    if (references.isDynamicMethod(context) && references.isGeneratedMessageLiteBuilder(clazz)) {
+      return register.getAsBoolean();
+    }
+    return false;
+  }
+
+  public static void addInliningHeuristicsForBuilderInlining(
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Set<DexMethod> alwaysInline,
+      Set<DexMethod> neverInline,
+      Set<DexMethod> bypassClinitforInlining) {
+    new RootSetExtension(appView, alwaysInline, neverInline, bypassClinitforInlining).extend();
+  }
+
+  private static class RootSetExtension {
+
+    private final AppView<? extends AppInfoWithSubtyping> appView;
+    private final ProtoReferences references;
+
+    private final Set<DexMethod> alwaysInline;
+    private final Set<DexMethod> neverInline;
+    private final Set<DexMethod> bypassClinitforInlining;
+
+    RootSetExtension(
+        AppView<? extends AppInfoWithSubtyping> appView,
+        Set<DexMethod> alwaysInline,
+        Set<DexMethod> neverInline,
+        Set<DexMethod> bypassClinitforInlining) {
+      this.appView = appView;
+      this.references = appView.protoShrinker().references;
+      this.alwaysInline = alwaysInline;
+      this.neverInline = neverInline;
+      this.bypassClinitforInlining = bypassClinitforInlining;
+    }
+
+    void extend() {
+      // GeneratedMessageLite heuristics.
+      alwaysInlineCreateBuilderFromGeneratedMessageLite();
+      neverInlineIsInitializedFromGeneratedMessageLite();
+
+      // * extends GeneratedMessageLite heuristics.
+      bypassClinitforInliningNewBuilderMethods();
+      alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations();
+
+      // GeneratedMessageLite$Builder heuristics.
+      alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder();
+    }
+
+    private void bypassClinitforInliningNewBuilderMethods() {
+      for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
+        DexProgramClass clazz = appView.definitionFor(type).asProgramClass();
+        if (clazz != null) {
+          DexEncodedMethod newBuilderMethod =
+              clazz.lookupDirectMethod(
+                  method -> method.method.name == references.newBuilderMethodName);
+          if (newBuilderMethod != null) {
+            bypassClinitforInlining.add(newBuilderMethod.method);
+          }
+        }
+      }
+    }
+
+    private void alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder() {
+      alwaysInline.add(references.generatedMessageLiteBuilderMethods.buildPartialMethod);
+    }
+
+    private void alwaysInlineCreateBuilderFromGeneratedMessageLite() {
+      alwaysInline.add(references.generatedMessageLiteMethods.createBuilderMethod);
+    }
+
+    private void alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations() {
+      // TODO(b/132600418): We should be able to determine that dynamicMethod() becomes 'SIMPLE'
+      //  when the MethodToInvoke argument is MethodToInvoke.NEW_BUILDER.
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
+        alwaysInline.add(
+            dexItemFactory.createMethod(
+                type,
+                dexItemFactory.createProto(
+                    dexItemFactory.objectType,
+                    references.methodToInvokeType,
+                    dexItemFactory.objectType,
+                    dexItemFactory.objectType),
+                "dynamicMethod"));
+      }
+    }
+
+    /**
+     * Without this rule, GeneratedMessageLite$Builder.build() becomes too big for class inlining.
+     * TODO(b/112437944): Maybe introduce a -neverinlineinto rule instead?
+     */
+    private void neverInlineIsInitializedFromGeneratedMessageLite() {
+      neverInline.add(references.generatedMessageLiteMethods.isInitializedMethod);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
new file mode 100644
index 0000000..4d04a73
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -0,0 +1,66 @@
+// 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.ir.analysis.proto;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.proto.ProtoReferences.MethodToInvokeMembers;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
+
+/**
+ * Equivalent to the {@link InliningReasonStrategy} in {@link #parent} except for invocations to
+ * dynamicMethod().
+ */
+public class ProtoInliningReasonStrategy implements InliningReasonStrategy {
+
+  private static final int METHOD_TO_INVOKE_ARGUMENT_POSITION_IN_DYNAMIC_METHOD = 1;
+
+  private final InliningReasonStrategy parent;
+  private final ProtoReferences references;
+
+  public ProtoInliningReasonStrategy(AppView<?> appView, InliningReasonStrategy parent) {
+    this.parent = parent;
+    this.references = appView.protoShrinker().references;
+  }
+
+  @Override
+  public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
+    return references.isDynamicMethod(target)
+        ? computeInliningReasonForDynamicMethod(invoke)
+        : parent.computeInliningReason(invoke, target);
+  }
+
+  private Reason computeInliningReasonForDynamicMethod(InvokeMethod invoke) {
+    Value methodToInvokeValue =
+        invoke
+            .inValues()
+            .get(METHOD_TO_INVOKE_ARGUMENT_POSITION_IN_DYNAMIC_METHOD)
+            .getAliasedValue();
+    if (methodToInvokeValue.isPhi()) {
+      return Reason.NEVER;
+    }
+
+    Instruction methodToInvokeDefinition = methodToInvokeValue.definition;
+    if (!methodToInvokeDefinition.isStaticGet()) {
+      return Reason.NEVER;
+    }
+
+    DexField field = methodToInvokeDefinition.asStaticGet().getField();
+    MethodToInvokeMembers methodToInvokeMembers = references.methodToInvokeMembers;
+    if (methodToInvokeMembers.isMethodToInvokeWithSimpleBody(field)) {
+      return Reason.ALWAYS;
+    }
+
+    assert field.holder != references.methodToInvokeType
+        || methodToInvokeMembers.isMethodToInvokeWithNonSimpleBody(field);
+
+    return Reason.NEVER;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 0919db9..cedb6ed 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -5,8 +5,10 @@
 package com.android.tools.r8.ir.analysis.proto;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -17,12 +19,18 @@
   public final DexType extensionRegistryLiteType;
   public final DexType generatedExtensionType;
   public final DexType generatedMessageLiteType;
+  public final DexType generatedMessageLiteBuilderType;
   public final DexType rawMessageInfoType;
   public final DexType messageLiteType;
   public final DexType methodToInvokeType;
 
+  public final GeneratedMessageLiteMethods generatedMessageLiteMethods;
+  public final GeneratedMessageLiteBuilderMethods generatedMessageLiteBuilderMethods;
+  public final MethodToInvokeMembers methodToInvokeMembers;
+
   public final DexString dynamicMethodName;
   public final DexString findLiteExtensionByNumberName;
+  public final DexString newBuilderMethodName;
 
   public final DexProto dynamicMethodProto;
   public final DexProto findLiteExtensionByNumberProto;
@@ -42,6 +50,9 @@
             factory.createString("Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;"));
     generatedMessageLiteType =
         factory.createType(factory.createString("Lcom/google/protobuf/GeneratedMessageLite;"));
+    generatedMessageLiteBuilderType =
+        factory.createType(
+            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$Builder;"));
     rawMessageInfoType =
         factory.createType(factory.createString("Lcom/google/protobuf/RawMessageInfo;"));
     messageLiteType = factory.createType(factory.createString("Lcom/google/protobuf/MessageLite;"));
@@ -52,6 +63,7 @@
     // Names.
     dynamicMethodName = factory.createString("dynamicMethod");
     findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
+    newBuilderMethodName = factory.createString("newBuilder");
 
     // Protos.
     dynamicMethodProto =
@@ -73,6 +85,10 @@
             factory.createProto(
                 factory.voidType, messageLiteType, factory.stringType, factory.objectArrayType),
             factory.constructorMethodName);
+
+    generatedMessageLiteMethods = new GeneratedMessageLiteMethods(factory);
+    generatedMessageLiteBuilderMethods = new GeneratedMessageLiteBuilderMethods(factory);
+    methodToInvokeMembers = new MethodToInvokeMembers(factory);
   }
 
   public boolean isDynamicMethod(DexMethod method) {
@@ -88,7 +104,87 @@
         && method.name.startsWith(findLiteExtensionByNumberName);
   }
 
+  public boolean isGeneratedMessageLiteBuilder(DexProgramClass clazz) {
+    return clazz.superType == generatedMessageLiteBuilderType;
+  }
+
   public boolean isMessageInfoConstructionMethod(DexMethod method) {
     return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
   }
+
+  class GeneratedMessageLiteMethods {
+
+    public final DexMethod createBuilderMethod;
+    public final DexMethod isInitializedMethod;
+
+    private GeneratedMessageLiteMethods(DexItemFactory dexItemFactory) {
+      createBuilderMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(generatedMessageLiteBuilderType),
+              "createBuilder");
+      isInitializedMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(dexItemFactory.booleanType),
+              "isInitialized");
+    }
+  }
+
+  class GeneratedMessageLiteBuilderMethods {
+
+    public final DexMethod buildPartialMethod;
+
+    private GeneratedMessageLiteBuilderMethods(DexItemFactory dexItemFactory) {
+      buildPartialMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteBuilderType,
+              dexItemFactory.createProto(generatedMessageLiteType),
+              "buildPartial");
+    }
+  }
+
+  public class MethodToInvokeMembers {
+
+    public final DexField buildMessageInfoField;
+    public final DexField getDefaultInstanceField;
+    public final DexField getMemoizedIsInitializedField;
+    public final DexField getParserField;
+    public final DexField newBuilderField;
+    public final DexField newMutableInstanceField;
+    public final DexField setMemoizedIsInitializedField;
+
+    private MethodToInvokeMembers(DexItemFactory dexItemFactory) {
+      buildMessageInfoField =
+          dexItemFactory.createField(methodToInvokeType, methodToInvokeType, "BUILD_MESSAGE_INFO");
+      getDefaultInstanceField =
+          dexItemFactory.createField(
+              methodToInvokeType, methodToInvokeType, "GET_DEFAULT_INSTANCE");
+      getMemoizedIsInitializedField =
+          dexItemFactory.createField(
+              methodToInvokeType, methodToInvokeType, "GET_MEMOIZED_IS_INITIALIZED");
+      getParserField =
+          dexItemFactory.createField(methodToInvokeType, methodToInvokeType, "GET_PARSER");
+      newBuilderField =
+          dexItemFactory.createField(methodToInvokeType, methodToInvokeType, "NEW_BUILDER");
+      newMutableInstanceField =
+          dexItemFactory.createField(
+              methodToInvokeType, methodToInvokeType, "NEW_MUTABLE_INSTANCE");
+      setMemoizedIsInitializedField =
+          dexItemFactory.createField(
+              methodToInvokeType, methodToInvokeType, "SET_MEMOIZED_IS_INITIALIZED");
+    }
+
+    public boolean isMethodToInvokeWithSimpleBody(DexField field) {
+      return field == getDefaultInstanceField
+          || field == getMemoizedIsInitializedField
+          || field == newBuilderField
+          || field == newMutableInstanceField
+          || field == setMemoizedIsInitializedField;
+    }
+
+    public boolean isMethodToInvokeWithNonSimpleBody(DexField field) {
+      return field == buildMessageInfoField || field == getParserField;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index bd7d362..d7ba0cc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -14,6 +14,7 @@
   public final ProtoFieldTypeFactory factory;
   public final GeneratedExtensionRegistryShrinker generatedExtensionRegistryShrinker;
   public final GeneratedMessageLiteShrinker generatedMessageLiteShrinker;
+  public final GeneratedMessageLiteBuilderShrinker generatedMessageLiteBuilderShrinker;
   public final ProtoReferences references;
 
   public ProtoShrinker(AppView<AppInfoWithLiveness> appView) {
@@ -29,6 +30,10 @@
         appView.options().protoShrinking().enableGeneratedMessageLiteShrinking
             ? new GeneratedMessageLiteShrinker(appView, decoder, references)
             : null;
+    this.generatedMessageLiteBuilderShrinker =
+        appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking
+            ? new GeneratedMessageLiteBuilderShrinker(references)
+            : null;
     this.references = references;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 27b3ede..fce7b09 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.analysis.proto.schema;
 
-import com.android.tools.r8.OptionalBool;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -25,6 +24,7 @@
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.shaking.KeepReason;
 import com.android.tools.r8.utils.BitUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
 import java.util.List;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 35c483b..3449c30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.analysis.sideeffect;
 
-import com.android.tools.r8.OptionalBool;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexType;
@@ -16,6 +15,7 @@
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.OptionalBool;
 
 public class ClassInitializerSideEffectAnalysis {
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 51196ea..2b05e0c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.google.common.annotations.VisibleForTesting;
@@ -76,6 +78,19 @@
     return new Position(-1, null, method, callerPosition, false);
   }
 
+  public static Position getPositionForInlining(
+      AppView<?> appView, InvokeMethod invoke, DexEncodedMethod context) {
+    Position position = invoke.getPosition();
+    if (position.method == null) {
+      assert position.isNone();
+      position = Position.noneWithMethod(context.method, null);
+    }
+    assert position.callerPosition == null
+        || position.getOutermostCaller().method
+            == appView.graphLense().getOriginalMethodSignature(context.method);
+    return position;
+  }
+
   public boolean isNone() {
     return line == -1;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 6af3446..189bb92 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -132,7 +132,10 @@
       if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
         // For virtual and interface calls add all potential targets that could be called.
         ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
-        resolutionResult.forEachTarget(target -> processInvokeWithDynamicDispatch(type, target));
+        DexEncodedMethod target = resolutionResult.getSingleTarget();
+        if (target != null) {
+          processInvokeWithDynamicDispatch(type, target);
+        }
       } else {
         DexEncodedMethod singleTarget =
             appView.appInfo().lookupSingleTarget(type, method, context.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 8907df8..55cabed 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -166,12 +165,15 @@
     return code;
   }
 
-  private static boolean verifyInvokeInterface(CfCode code, DexDefinitionSupplier definitions) {
+  private static boolean verifyInvokeInterface(CfCode code, AppView<?> appView) {
+    if (appView.options().testing.allowInvokeErrors) {
+      return true;
+    }
     for (CfInstruction instruction : code.instructions) {
       if (instruction instanceof CfInvoke) {
         CfInvoke invoke = (CfInvoke) instruction;
         if (invoke.getMethod().holder.isClassType()) {
-          DexClass holder = definitions.definitionFor(invoke.getMethod().holder);
+          DexClass holder = appView.definitionFor(invoke.getMethod().holder);
           assert holder == null || holder.isInterface() == invoke.isInterface();
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 28e95f8..a7c90fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -39,8 +39,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -251,6 +249,10 @@
             && method.accessFlags.isSynchronized();
   }
 
+  public Origin getOrigin() {
+    return origin;
+  }
+
   public DexType getOriginalHolder() {
     return code.getOriginalHolder();
   }
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 9935044..7a64973 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
@@ -209,6 +209,7 @@
       // InterfaceMethodRewriter is needed for emulated interfaces.
       // LambdaRewriter is needed because if it is missing there are invoke custom on
       // default/static interface methods, and this is not supported by the compiler.
+      // DesugaredLibraryAPIConverter is here to duplicate APIs.
       // The rest is nulled out. In addition the rewriting logic fails without lambda rewriting.
       this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
       this.interfaceMethodRewriter =
@@ -216,6 +217,7 @@
               ? null
               : new InterfaceMethodRewriter(appView, this);
       this.lambdaRewriter = new LambdaRewriter(appView, this);
+      this.desugaredLibraryAPIConverter = new DesugaredLibraryAPIConverter(appView);
       this.twrCloseResourceRewriter = null;
       this.lambdaMerger = null;
       this.covariantReturnTypeAnnotationTransformer = null;
@@ -234,7 +236,6 @@
       this.typeChecker = null;
       this.d8NestBasedAccessDesugaring = null;
       this.stringSwitchRemover = null;
-      this.desugaredLibraryAPIConverter = null;
       this.serviceLoaderRewriter = null;
       this.methodOptimizationInfoCollector = null;
       return;
@@ -662,6 +663,8 @@
       assert graphLenseForIR == appView.graphLense();
     }
 
+    // Assure that no more optimization feedback left after primary processing.
+    assert feedback.noUpdatesLeft();
     appView.setAllCodeProcessed();
 
     if (libraryMethodOverrideAnalysis != null) {
@@ -784,6 +787,7 @@
       }
     }
 
+    // Assure that no more optimization feedback left after post processing.
     assert feedback.noUpdatesLeft();
 
     // Check if what we've added to the application builder as synthesized classes are same as
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 e321f56..11c9239 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
@@ -81,7 +81,7 @@
     }
 
     // When inheriting from a library class, the library class may implement interfaces to
-    // desugar. We therefore need to look the interfaces of the library classes.
+    // desugar. We therefore need to look at the interfaces of the library classes.
     boolean desugaredLibraryLookup =
         superClass != null
             && superClass.isLibraryClass()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index aebe043..4483eca 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -130,16 +130,19 @@
       return;
     }
     DexMethod method = code.method.method;
-    if (appView.rewritePrefix.hasRewrittenType(method.holder) || method.holder.isArrayType()) {
+    if (method.holder.isArrayType()
+        || !appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto)
+        || appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getEmulateLibraryInterface()
+            .containsKey(method.holder)) {
       return;
     }
     DexClass dexClass = appView.definitionFor(method.holder);
     if (dexClass == null) {
       return;
     }
-    if (!appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto)) {
-      return;
-    }
     if (overridesLibraryMethod(dexClass, method)) {
       generateCallBack(dexClass, code.method);
     }
@@ -164,17 +167,13 @@
       if (dexClass.superType != factory.objectType) {
         workList.add(dexClass.superType);
       }
-      if (!dexClass.isLibraryClass()) {
+      if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
         continue;
       }
       DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method);
       if (dexEncodedMethod != null) {
-        if (appView
-                .options()
-                .desugaredLibraryConfiguration
-                .getEmulateLibraryInterface()
-                .containsKey(dexClass.type)
-            || appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
+        // In this case, the object will be wrapped.
+        if (appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
           return false;
         }
         foundOverrideToRewrite = true;
@@ -184,14 +183,10 @@
   }
 
   private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
-    if (trackedCallBackAPIs != null) {
-      trackedCallBackAPIs.add(originalMethod.method);
-    }
-    DexMethod methodToInstall =
-        methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
     if (dexClass.isInterface()
         && originalMethod.isDefaultMethod()
-        && !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
+        && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
+            || appView.options().isDesugaredLibraryCompilation())) {
       // Interface method desugaring has been performed before and all the call-backs will be
       // generated in all implementors of the interface. R8 cannot introduce new
       // default methods at this point, but R8 does not need to do anything (the interface
@@ -199,17 +194,14 @@
       // support the call-back correctly).
       return;
     }
-    CfCode cfCode =
-        new APIConverterWrapperCfCodeProvider(
-            appView, originalMethod.method, null, this, dexClass.isInterface())
-            .generateCfCode();
-    DexEncodedMethod newDexEncodedMethod =
-        wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
-    newDexEncodedMethod.setCode(cfCode, appView);
-    addCallBackSignature(dexClass, newDexEncodedMethod);
+    if (trackedCallBackAPIs != null) {
+      trackedCallBackAPIs.add(originalMethod.method);
+    }
+    addCallBackSignature(dexClass, originalMethod);
   }
 
   private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
+    assert dexClass.type == method.method.holder;
     callBackMethods.putIfAbsent(dexClass, new HashSet<>());
     callBackMethods.get(dexClass).add(method);
   }
@@ -240,12 +232,31 @@
       generateTrackDesugaredAPIWarnings(trackedAPIs, "");
       generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
     }
-    wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
     for (DexClass dexClass : callBackMethods.keySet()) {
-      Set<DexEncodedMethod> dexEncodedMethods = callBackMethods.get(dexClass);
+      Set<DexEncodedMethod> dexEncodedMethods =
+          generateCallbackMethods(callBackMethods.get(dexClass), dexClass);
       dexClass.appendVirtualMethods(dexEncodedMethods);
       irConverter.processMethodsConcurrently(dexEncodedMethods, executorService);
     }
+    wrapperSynthesizor.finalizeWrappers(builder, irConverter, executorService);
+  }
+
+  private Set<DexEncodedMethod> generateCallbackMethods(
+      Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
+    Set<DexEncodedMethod> newDexEncodedMethods = new HashSet<>();
+    for (DexEncodedMethod originalMethod : originalMethods) {
+      DexMethod methodToInstall =
+          methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+      CfCode cfCode =
+          new APIConverterWrapperCfCodeProvider(
+                  appView, originalMethod.method, null, this, dexClass.isInterface())
+              .generateCfCode();
+      DexEncodedMethod newDexEncodedMethod =
+          wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+      newDexEncodedMethod.setCode(cfCode, appView);
+      newDexEncodedMethods.add(newDexEncodedMethod);
+    }
+    return newDexEncodedMethods;
   }
 
   private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index e40252f..5d7c31f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -137,7 +136,7 @@
     if (dexClass == null) {
       return false;
     }
-    return dexClass.isLibraryClass();
+    return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
   }
 
   DexType getTypeWrapper(DexType type) {
@@ -153,9 +152,10 @@
   }
 
   private DexType createWrapperType(DexType type, String suffix) {
+    String prefix = appView.options().isDesugaredLibraryCompilation() ? "lib$" : "";
     return factory.createType(
         DescriptorUtils.javaTypeToDescriptor(
-            WRAPPER_PREFIX + type.toString().replace('.', '$') + suffix));
+            WRAPPER_PREFIX + prefix + type.toString().replace('.', '$') + suffix));
   }
 
   private DexType getWrapper(
@@ -183,7 +183,8 @@
       assert pair.getSecond() == null;
       DexClass dexClass = appView.definitionFor(type);
       // The dexClass should be a library class, so it cannot be null.
-      assert dexClass != null && dexClass.isLibraryClass();
+      assert dexClass != null
+          && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
       if (dexClass.accessFlags.isFinal()) {
         throw appView
             .options()
@@ -209,7 +210,7 @@
     return synthesizeWrapper(
         vivifiedTypeFor(type),
         dexClass,
-        synthesizeVirtualMethodsForTypeWrapper(dexClass.asLibraryClass(), wrapperField),
+        synthesizeVirtualMethodsForTypeWrapper(dexClass, wrapperField),
         wrapperField);
   }
 
@@ -221,7 +222,7 @@
     return synthesizeWrapper(
         type,
         dexClass,
-        synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass.asLibraryClass(), wrapperField),
+        synthesizeVirtualMethodsForVivifiedTypeWrapper(dexClass, wrapperField),
         wrapperField);
   }
 
@@ -274,7 +275,7 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForVivifiedTypeWrapper(
-      DexLibraryClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass, DexEncodedField wrapperField) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only types in their signature, but each method the wrapper forwards
@@ -318,7 +319,7 @@
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
-      DexLibraryClass dexClass, DexEncodedField wrapperField) {
+      DexClass dexClass, DexEncodedField wrapperField) {
     List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Each method should use only vivified types in their signature, but each method the wrapper
@@ -334,7 +335,8 @@
     Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
       DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
-      assert holderClass != null;
+      assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+      boolean isInterface = holderClass == null || holderClass.isInterface();
       DexMethod methodToInstall =
           DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
               dexEncodedMethod.method, wrapperField.field.holder, appView);
@@ -346,11 +348,7 @@
       } else {
         cfCode =
             new APIConverterWrapperCfCodeProvider(
-                    appView,
-                    dexEncodedMethod.method,
-                    wrapperField.field,
-                    converter,
-                    holderClass.isInterface())
+                    appView, dexEncodedMethod.method, wrapperField.field, converter, isInterface)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -399,7 +397,7 @@
         code);
   }
 
-  private List<DexEncodedMethod> allImplementedMethods(DexLibraryClass libraryClass) {
+  private List<DexEncodedMethod> allImplementedMethods(DexClass libraryClass) {
     LinkedList<DexClass> workList = new LinkedList<>();
     List<DexEncodedMethod> implementedMethods = new ArrayList<>();
     workList.add(libraryClass);
@@ -423,8 +421,11 @@
       }
       for (DexType itf : dexClass.interfaces.values) {
         DexClass itfClass = appView.definitionFor(itf);
-        assert itfClass != null; // Cannot be null since we started from a LibraryClass.
-        workList.add(itfClass);
+        // Cannot be null in program since we started from a LibraryClass.
+        assert itfClass != null || appView.options().isDesugaredLibraryCompilation();
+        if (itfClass != null) {
+          workList.add(itfClass);
+        }
       }
       if (dexClass.superType != factory.objectType) {
         DexClass superClass = appView.definitionFor(dexClass.superType);
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 be007bb..e1c583d 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
@@ -3125,9 +3125,12 @@
         if (!enumClazz.accessFlags.isFinal()) {
           continue;
         }
-        if (appView.appInfo()
-            .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
-            .asResultOfResolve().method != dexItemFactory.enumMethods.toString) {
+        if (appView
+                .appInfo()
+                .resolveMethodOnClass(valueInfo.type, dexItemFactory.objectMethods.toString)
+                .getSingleTarget()
+                .method
+            != dexItemFactory.enumMethods.toString) {
           continue;
         }
         iterator.replaceCurrentInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index fea8d6c..6b7c389 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -26,12 +27,13 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
+import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,8 +57,8 @@
   private final DexEncodedMethod method;
   private final IRCode code;
   private final MethodProcessor methodProcessor;
-  private final CallSiteInformation callSiteInformation;
   private final Predicate<DexEncodedMethod> isProcessedConcurrently;
+  private final InliningReasonStrategy reasonStrategy;
   private final int inliningInstructionLimit;
   private int instructionAllowance;
 
@@ -73,10 +75,17 @@
     this.method = method;
     this.code = code;
     this.methodProcessor = methodProcessor;
-    this.callSiteInformation = methodProcessor.getCallSiteInformation();
     this.isProcessedConcurrently = methodProcessor::isProcessedConcurrently;
     this.inliningInstructionLimit = inliningInstructionLimit;
     this.instructionAllowance = inliningInstructionAllowance;
+
+    DefaultInliningReasonStrategy defaultInliningReasonStrategy =
+        new DefaultInliningReasonStrategy(
+            appView, methodProcessor.getCallSiteInformation(), inliner);
+    this.reasonStrategy =
+        appView.withGeneratedMessageLiteShrinker(
+            ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
+            defaultInliningReasonStrategy);
   }
 
   @Override
@@ -128,33 +137,6 @@
     return false;
   }
 
-  private Reason computeInliningReason(DexEncodedMethod target) {
-    if (target.getOptimizationInfo().forceInline()
-        || (appView.appInfo().hasLiveness()
-            && appView.withLiveness().appInfo().forceInline.contains(target.method))) {
-      assert !appView.appInfo().neverInline.contains(target.method);
-      return Reason.FORCE;
-    }
-    if (appView.appInfo().hasLiveness()
-        && appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
-      return Reason.ALWAYS;
-    }
-    if (appView.options().disableInliningOfLibraryMethodOverrides
-        && target.isLibraryMethodOverride().isTrue()) {
-      // This method will always have an implicit call site from the library, so we won't be able to
-      // remove it after inlining even if we have single or dual call site information from the
-      // program.
-      return Reason.SIMPLE;
-    }
-    if (callSiteInformation.hasSingleCallSite(target.method)) {
-      return Reason.SINGLE_CALLER;
-    }
-    if (isDoubleInliningTarget(target)) {
-      return Reason.DUAL_CALLER;
-    }
-    return Reason.SIMPLE;
-  }
-
   private boolean canInlineStaticInvoke(
       InvokeStatic invoke,
       DexEncodedMethod method,
@@ -203,16 +185,14 @@
       return true;
     }
 
+    if (appView.rootSet().bypassClinitForInlining.contains(target.method)) {
+      return true;
+    }
+
     whyAreYouNotInliningReporter.reportMustTriggerClassInitialization();
     return false;
   }
 
-  private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
-    // 10 is found from measuring.
-    return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
-        && candidate.getCode().estimatedSizeForInliningAtMost(10);
-  }
-
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
@@ -364,7 +344,11 @@
       return null;
     }
 
-    Reason reason = computeInliningReason(singleTarget);
+    Reason reason = reasonStrategy.computeInliningReason(invoke, singleTarget);
+    if (reason == Reason.NEVER) {
+      return null;
+    }
+
     if (!singleTarget.isInliningCandidate(
         method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index ec28d2d..c791772 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -24,12 +24,12 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final DexEncodedMethod method;
-  private final Map<InvokeMethod, Inliner.InliningInfo> invokesToInline;
+  private final Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline;
 
   ForcedInliningOracle(
       AppView<AppInfoWithLiveness> appView,
       DexEncodedMethod method,
-      Map<InvokeMethod, Inliner.InliningInfo> invokesToInline) {
+      Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline) {
     this.appView = appView;
     this.method = method;
     this.invokesToInline = invokesToInline;
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 70013de..c3fe726 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
@@ -37,8 +37,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -46,11 +44,11 @@
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.InternalOptions;
@@ -89,7 +87,10 @@
       LensCodeRewriter lensCodeRewriter) {
     Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
     this.appView = appView;
-    this.blacklist = ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
+    this.blacklist =
+        appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations
+            ? ImmutableSet.of()
+            : ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
     this.lambdaMerger = lambdaMerger;
     this.lensCodeRewriter = lensCodeRewriter;
     this.mainDexClasses = mainDexClasses;
@@ -202,10 +203,8 @@
     return target.isSamePackage(context);
   }
 
-  synchronized boolean isDoubleInliningTarget(
-      CallSiteInformation callSiteInformation, DexEncodedMethod candidate) {
-    return callSiteInformation.hasDoubleCallSite(candidate.method)
-        || doubleInlineSelectedTargets.contains(candidate);
+  public synchronized boolean isDoubleInlineSelectedTarget(DexEncodedMethod method) {
+    return doubleInlineSelectedTargets.contains(method);
   }
 
   synchronized boolean satisfiesRequirementsForDoubleInlining(
@@ -546,7 +545,8 @@
     ALWAYS,        // Inlinee is marked for inlining due to alwaysinline directive.
     SINGLE_CALLER, // Inlinee has precisely one caller.
     DUAL_CALLER,   // Inlinee has precisely two callers.
-    SIMPLE;        // Inlinee has simple code suitable for inlining.
+    SIMPLE,        // Inlinee has simple code suitable for inlining.
+    NEVER;         // Inlinee must not be inlined.
 
     public boolean mustBeInlined() {
       // TODO(118734615): Include SINGLE_CALLER and DUAL_CALLER here as well?
@@ -573,18 +573,17 @@
     }
 
     InlineeWithReason buildInliningIR(
-        DexEncodedMethod context,
-        ValueNumberGenerator generator,
         AppView<? extends AppInfoWithSubtyping> appView,
-        Position callerPosition,
+        InvokeMethod invoke,
+        DexEncodedMethod context,
+        InliningIRProvider inliningIRProvider,
         LambdaMerger lambdaMerger,
         LensCodeRewriter lensCodeRewriter) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       InternalOptions options = appView.options();
-      Origin origin = appView.appInfo().originFor(target.method.holder);
 
       // Build the IR for a yet not processed method, and perform minimal IR processing.
-      IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
+      IRCode code = inliningIRProvider.getInliningIR(invoke, target);
 
       // Insert a null check if this is needed to preserve the implicit null check for the receiver.
       // This is only needed if we do not also insert a monitor-enter instruction, since that will
@@ -813,10 +812,11 @@
   public void performForcedInlining(
       DexEncodedMethod method,
       IRCode code,
-      Map<InvokeMethod, InliningInfo> invokesToInline) {
-
+      Map<? extends InvokeMethod, InliningInfo> invokesToInline,
+      InliningIRProvider inliningIRProvider) {
     ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
-    performInliningImpl(oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance());
+    performInliningImpl(
+        oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance(), inliningIRProvider);
   }
 
   public void performInlining(
@@ -832,7 +832,9 @@
             methodProcessor,
             options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code));
-    performInliningImpl(oracle, oracle, method, code, feedback);
+    InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+    assert inliningIRProvider.verifyIRCacheIsEmpty();
+    performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider);
   }
 
   public DefaultInliningOracle createDefaultOracle(
@@ -856,7 +858,8 @@
       InliningOracle oracle,
       DexEncodedMethod context,
       IRCode code,
-      OptimizationFeedback feedback) {
+      OptimizationFeedback feedback,
+      InliningIRProvider inliningIRProvider) {
     AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
     Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -910,12 +913,7 @@
 
           InlineeWithReason inlinee =
               action.buildInliningIR(
-                  context,
-                  code.valueNumberGenerator,
-                  appView,
-                  getPositionForInlining(invoke, context),
-                  lambdaMerger,
-                  lensCodeRewriter);
+                  appView, invoke, context, inliningIRProvider, lambdaMerger, lensCodeRewriter);
           if (strategy.willExceedBudget(
               code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
@@ -997,18 +995,6 @@
     assert code.isConsistentSSA();
   }
 
-  private Position getPositionForInlining(InvokeMethod invoke, DexEncodedMethod context) {
-    Position position = invoke.getPosition();
-    if (position.method == null) {
-      assert position.isNone();
-      position = Position.noneWithMethod(context.method, null);
-    }
-    assert position.callerPosition == null
-        || position.getOutermostCaller().method
-            == appView.graphLense().getOriginalMethodSignature(context.method);
-    return position;
-  }
-
   private boolean useReflectiveOperationExceptionOrUnknownClassInCatch(IRCode code) {
     for (BasicBlock block : code.blocks) {
       for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) {
@@ -1023,18 +1009,18 @@
     return false;
   }
 
-  private static DexType getDowncastTypeIfNeeded(
+  private DexType getDowncastTypeIfNeeded(
       InliningStrategy strategy, InvokeMethod invoke, DexEncodedMethod target) {
     if (invoke.isInvokeMethodWithReceiver()) {
       // If the invoke has a receiver but the actual type of the receiver is different
       // from the computed target holder, inlining requires a downcast of the receiver.
-      DexType assumedReceiverType = strategy.getReceiverTypeIfKnown(invoke);
-      if (assumedReceiverType == null) {
+      DexType receiverType = strategy.getReceiverTypeIfKnown(invoke);
+      if (receiverType == null) {
         // In case we don't know exact type of the receiver we use declared
         // method holder as a fallback.
-        assumedReceiverType = invoke.getInvokedMethod().holder;
+        receiverType = invoke.getInvokedMethod().holder;
       }
-      if (assumedReceiverType != target.method.holder) {
+      if (!appView.appInfo().isSubtype(receiverType, target.method.holder)) {
         return target.method.holder;
       }
     }
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 f1256d0..4977d4c 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
@@ -356,7 +356,7 @@
       return ConstraintWithTarget.NEVER;
     }
 
-    DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
+    DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
     if (resolutionTarget == null) {
       // This will fail at runtime.
       return ConstraintWithTarget.NEVER;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index c4d7051..2945c6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -226,7 +227,8 @@
         }
 
         // Inline the class instance.
-        anyInlinedMethods |= processor.processInlining(code, defaultOracle);
+        InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+        anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
 
         // Restore normality.
         Set<Value> affectedValues = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 9bad45e..096699b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -39,21 +39,25 @@
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -75,8 +79,8 @@
   private DexClass eligibleClassDefinition;
   private boolean isDesugaredLambda;
 
-  private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
-      = new IdentityHashMap<>();
+  private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
+      new IdentityHashMap<>();
   private final Map<InvokeMethod, InliningInfo> extraMethodCalls
       = new IdentityHashMap<>();
   private final List<Pair<InvokeMethod, Integer>> unusedArguments
@@ -84,6 +88,11 @@
 
   private int estimatedCombinedSizeForInlining = 0;
 
+  // Set of values that may be an alias of the "root" instance (including the root instance itself).
+  // TODO(b/144825216): Distinguish the "may-aliases" from the "must-aliases" such that the cost
+  //  analysis is not optimistic.
+  private final Set<Value> receivers;
+
   InlineCandidateProcessor(
       AppView<AppInfoWithLiveness> appView,
       LambdaRewriter lambdaRewriter,
@@ -99,12 +108,17 @@
     this.method = method;
     this.root = root;
     this.isProcessedConcurrently = isProcessedConcurrently;
+    this.receivers = SetUtils.newIdentityHashSet(root.outValue());
   }
 
   int getEstimatedCombinedSizeForInlining() {
     return estimatedCombinedSizeForInlining;
   }
 
+  Set<Value> getReceivers() {
+    return receivers;
+  }
+
   // Checks if the root instruction defines eligible value, i.e. the value
   // exists and we have a definition of its class.
   EligibilityStatus isInstanceEligible() {
@@ -258,7 +272,7 @@
    */
   InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) {
     // No Phi users.
-    if (eligibleInstance.numberOfPhiUsers() > 0) {
+    if (eligibleInstance.hasPhiUsers()) {
       return eligibleInstance.firstPhiUser(); // Not eligible.
     }
 
@@ -267,10 +281,12 @@
       Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
       for (Instruction user : currentUsers) {
         if (user.isAssume()) {
-          if (user.outValue().numberOfPhiUsers() > 0) {
-            return user.outValue().firstPhiUser(); // Not eligible.
+          Value alias = user.outValue();
+          if (alias.hasPhiUsers()) {
+            return alias.firstPhiUser(); // Not eligible.
           }
-          indirectUsers.addAll(user.outValue().uniqueUsers());
+          receivers.add(alias);
+          indirectUsers.addAll(alias.uniqueUsers());
           continue;
         }
         // Field read/write.
@@ -303,9 +319,9 @@
                       && root.outValue() == invoke.getReceiver();
               if (isCorrespondingConstructorCall) {
                 InliningInfo inliningInfo =
-                    isEligibleConstructorCall(user.asInvokeDirect(), singleTarget, defaultOracle);
+                    isEligibleConstructorCall(invoke, singleTarget, defaultOracle);
                 if (inliningInfo != null) {
-                  methodCallsOnInstance.put(user.asInvokeDirect(), inliningInfo);
+                  methodCallsOnInstance.put(invoke, inliningInfo);
                   continue;
                 }
               }
@@ -365,12 +381,13 @@
   //  * remove root instruction
   //
   // Returns `true` if at least one method was inlined.
-  boolean processInlining(IRCode code, Supplier<InliningOracle> defaultOracle) {
+  boolean processInlining(
+      IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider) {
     // Verify that `eligibleInstance` is not aliased.
     assert eligibleInstance == eligibleInstance.getAliasedValue();
     replaceUsagesAsUnusedArgument(code);
 
-    boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code);
+    boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
     if (anyInlinedMethods) {
       // Reset the collections.
       methodCallsOnInstance.clear();
@@ -396,7 +413,7 @@
           : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
     }
 
-    anyInlinedMethods |= forceInlineDirectMethodInvocations(code);
+    anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
     removeAssumeInstructionsLinkedToEligibleInstance();
     removeMiscUsages(code);
     removeFieldReads(code);
@@ -421,20 +438,25 @@
     unusedArguments.clear();
   }
 
-  private boolean forceInlineExtraMethodInvocations(IRCode code) {
+  private boolean forceInlineExtraMethodInvocations(
+      IRCode code, InliningIRProvider inliningIRProvider) {
     if (extraMethodCalls.isEmpty()) {
       return false;
     }
     // Inline extra methods.
-    inliner.performForcedInlining(method, code, extraMethodCalls);
+    inliner.performForcedInlining(method, code, extraMethodCalls, inliningIRProvider);
     return true;
   }
 
-  private boolean forceInlineDirectMethodInvocations(IRCode code) {
+  private boolean forceInlineDirectMethodInvocations(
+      IRCode code, InliningIRProvider inliningIRProvider) {
     if (methodCallsOnInstance.isEmpty()) {
       return false;
     }
-    inliner.performForcedInlining(method, code, methodCallsOnInstance);
+    assert methodCallsOnInstance.keySet().stream()
+        .map(InvokeMethodWithReceiver::getReceiver)
+        .allMatch(receivers::contains);
+    inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
     return true;
   }
 
@@ -446,6 +468,7 @@
       Assume<?> assumeInstruction = user.asAssume();
       Value src = assumeInstruction.src();
       Value dest = assumeInstruction.outValue();
+      assert receivers.contains(dest);
       assert !dest.hasPhiUsers();
       dest.replaceUsers(src);
       removeInstruction(user);
@@ -515,17 +538,21 @@
 
   // Replace field reads with appropriate values, insert phis when needed.
   private void removeFieldReads(IRCode code) {
-    Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
+    TreeSet<InstanceGet> uniqueInstanceGetUsersWithDeterministicOrder =
+        new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       if (user.isInstanceGet()) {
-        // Replace a field read with appropriate value.
-        replaceFieldRead(code, user.asInstanceGet(), fieldHelpers);
+        if (user.outValue().hasAnyUsers()) {
+          uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet());
+        } else {
+          removeInstruction(user);
+        }
         continue;
       }
 
       if (user.isInstancePut()) {
-        // Skip in this iteration since these instructions are needed to
-        // properly calculate what value should field reads be replaced with.
+        // Skip in this iteration since these instructions are needed to properly calculate what
+        // value should field reads be replaced with.
         continue;
       }
 
@@ -535,6 +562,12 @@
               + "` after inlining: "
               + user);
     }
+
+    Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
+    for (InstanceGet user : uniqueInstanceGetUsersWithDeterministicOrder) {
+      // Replace a field read with appropriate value.
+      replaceFieldRead(code, user, fieldHelpers);
+    }
   }
 
   private void replaceFieldRead(
@@ -647,7 +680,7 @@
   // - if it is a regular chaining pattern where the only users of the out value are receivers to
   //   other invocations. In that case, we should add all indirect users of the out value to ensure
   //   they can also be inlined.
-  private static boolean isEligibleInvokeWithAllUsersAsReceivers(
+  private boolean isEligibleInvokeWithAllUsersAsReceivers(
       ClassInlinerEligibility eligibility,
       InvokeMethodWithReceiver invoke,
       Set<Instruction> indirectUsers) {
@@ -667,6 +700,10 @@
       return false;
     }
 
+    // Since the invoke-instruction may return the receiver, the out-value may be an alias of the
+    // receiver.
+    receivers.add(outValue);
+
     Set<Instruction> currentUsers = outValue.uniqueUsers();
     while (!currentUsers.isEmpty()) {
       Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
@@ -676,6 +713,7 @@
           if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
             return false;
           }
+          receivers.add(outValueAlias);
           indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9161f51..13844a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -670,6 +670,9 @@
   // declare a method called checkParameterIsNotNull(parameter, message) or
   // throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
   private static boolean isKotlinNullCheck(Instruction instr, Value value, AppView<?> appView) {
+    if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+      return false;
+    }
     if (!instr.isInvokeStatic()) {
       return false;
     }
@@ -814,11 +817,12 @@
             appView
                 .appInfo()
                 .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMethods.finalize);
-        for (DexEncodedMethod target : resolutionResult.asListOfTargets()) {
-          if (target.method != dexItemFactory.enumMethods.finalize
-              && target.method != dexItemFactory.objectMethods.finalize) {
+
+        DexEncodedMethod target = resolutionResult.getSingleTarget();
+        if (target != null
+            && target.method != dexItemFactory.enumMethods.finalize
+            && target.method != dexItemFactory.objectMethods.finalize) {
             return true;
-          }
         }
         return false;
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
new file mode 100644
index 0000000..f28afbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -0,0 +1,66 @@
+// 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.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class DefaultInliningReasonStrategy implements InliningReasonStrategy {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final CallSiteInformation callSiteInformation;
+  private final Inliner inliner;
+
+  public DefaultInliningReasonStrategy(
+      AppView<AppInfoWithLiveness> appView,
+      CallSiteInformation callSiteInformation,
+      Inliner inliner) {
+    this.appView = appView;
+    this.callSiteInformation = callSiteInformation;
+    this.inliner = inliner;
+  }
+
+  @Override
+  public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
+    if (target.getOptimizationInfo().forceInline()
+        || (appView.appInfo().hasLiveness()
+            && appView.withLiveness().appInfo().forceInline.contains(target.method))) {
+      assert !appView.appInfo().neverInline.contains(target.method);
+      return Reason.FORCE;
+    }
+    if (appView.appInfo().hasLiveness()
+        && appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
+      return Reason.ALWAYS;
+    }
+    if (appView.options().disableInliningOfLibraryMethodOverrides
+        && target.isLibraryMethodOverride().isTrue()) {
+      // This method will always have an implicit call site from the library, so we won't be able to
+      // remove it after inlining even if we have single or dual call site information from the
+      // program.
+      return Reason.SIMPLE;
+    }
+    if (callSiteInformation.hasSingleCallSite(target.method)) {
+      return Reason.SINGLE_CALLER;
+    }
+    if (isDoubleInliningTarget(target)) {
+      return Reason.DUAL_CALLER;
+    }
+    return Reason.SIMPLE;
+  }
+
+  private boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
+    // 10 is found from measuring.
+    if (callSiteInformation.hasDoubleCallSite(candidate.method)
+        || inliner.isDoubleInlineSelectedTarget(candidate)) {
+      return candidate.getCode().estimatedSizeForInliningAtMost(10);
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
new file mode 100644
index 0000000..580e1b2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -0,0 +1,50 @@
+// 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.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.origin.Origin;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class InliningIRProvider {
+
+  private final AppView<?> appView;
+  private final DexEncodedMethod context;
+  private final ValueNumberGenerator valueNumberGenerator;
+
+  private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
+
+  public InliningIRProvider(AppView<?> appView, DexEncodedMethod context, IRCode code) {
+    this.appView = appView;
+    this.context = context;
+    this.valueNumberGenerator = code.valueNumberGenerator;
+  }
+
+  public IRCode getInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
+    IRCode cached = cache.remove(invoke);
+    if (cached != null) {
+      return cached;
+    }
+    Position position = Position.getPositionForInlining(appView, invoke, context);
+    Origin origin = appView.appInfo().originFor(method.method.holder);
+    return method.buildInliningIR(context, appView, valueNumberGenerator, position, origin);
+  }
+
+  public void cacheInliningIR(InvokeMethod invoke, IRCode code) {
+    IRCode existing = cache.put(invoke, code);
+    assert existing == null;
+  }
+
+  public boolean verifyIRCacheIsEmpty() {
+    assert cache.isEmpty();
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
new file mode 100644
index 0000000..5450988
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
@@ -0,0 +1,14 @@
+// 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.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+
+public interface InliningReasonStrategy {
+
+  Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 9a4565c..b9ef1c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -562,14 +560,12 @@
           Value outValue = invoke.outValue();
           DexType returnType = method.proto.returnType;
           Value newOutValue =
-              returnType.isVoidType()
+              returnType.isVoidType() || outValue == null
                   ? null
                   : code.createValue(
                       TypeLatticeElement.fromDexType(
-                          returnType,
-                          outValue == null ? maybeNull() : outValue.getTypeLattice().nullability(),
-                          appView),
-                      outValue == null ? null : outValue.getLocalInfo());
+                          returnType, outValue.getTypeLattice().nullability(), appView),
+                      outValue.getLocalInfo());
           it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
         }
         continue;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 92ba6a0..a83c407 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,11 +4,22 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
 
+  private KmClass kmClass;
+
   static KotlinClass fromKotlinClassMetadata(
       KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
@@ -24,7 +35,35 @@
   void processMetadata() {
     assert !isProcessed;
     isProcessed = true;
-    // TODO(b/70169921): once migration is complete, use #toKmClass and store a mutable model.
+    kmClass = metadata.toKmClass();
+  }
+
+  @Override
+  void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    kmClass.getSupertypes().removeIf(
+        kmType -> {
+          Box<Boolean> isLive = new Box<>(false);
+          kmType.accept(new KmTypeVisitor() {
+            @Override
+            public void visitClass(String name) {
+              String descriptor = DescriptorUtils.javaTypeToDescriptorIfValidJavaType(name);
+              if (descriptor != null) {
+                DexType type = appView.dexItemFactory().createType(name);
+                DexType renamedType = lens.lookupType(type, appView.dexItemFactory());
+                isLive.set(appView.appInfo().isLiveProgramType(renamedType));
+              }
+            }
+          });
+          return !isLive.get();
+        }
+    );
+  }
+
+  @Override
+  KotlinClassHeader createHeader() {
+    KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
+    kmClass.accept(writer);
+    return writer.write().getHeader();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 0bd545c..6429a50 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,6 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
@@ -27,6 +32,16 @@
   }
 
   @Override
+  void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    throw new Unreachable(toString());
+  }
+
+  @Override
+  KotlinClassHeader createHeader() {
+    throw new Unreachable(toString());
+  }
+
+  @Override
   public Kind getKind() {
     return Kind.Facade;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 6004201..dc3161c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -79,11 +79,11 @@
     if (kMetadata instanceof KotlinClassMetadata.Class) {
       return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
-      return KotlinFile.fromKotlinClassMetadata(kMetadata);
+      return KotlinFile.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
       return KotlinClassFacade.fromKotlinClassMetadata(kMetadata);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
-      return KotlinClassPart.fromKotlinClassMetdata(kMetadata);
+      return KotlinClassPart.fromKotlinClassMetadata(kMetadata);
     } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
       return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
     } else {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 1ae3db8..96c1022 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,11 +4,18 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.KmPackage;
+import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
 
-  static KotlinClassPart fromKotlinClassMetdata(KotlinClassMetadata kotlinClassMetadata) {
+  private KmPackage kmPackage;
+
+  static KotlinClassPart fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart;
     KotlinClassMetadata.MultiFileClassPart multiFileClassPart =
         (KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata;
@@ -23,7 +30,20 @@
   void processMetadata() {
     assert !isProcessed;
     isProcessed = true;
-    // TODO(b/70169921): once migration is complete, use #toKmPackage and store a mutable model.
+    kmPackage = metadata.toKmPackage();
+  }
+
+  @Override
+  void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    // TODO(b/70169921): no idea yet!
+    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+        : toString();
+  }
+
+  @Override
+  KotlinClassHeader createHeader() {
+    // TODO(b/70169921): may need to update if `rewrite` is implemented.
+    return metadata.getHeader();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index c22dcdb..e2bb332 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,26 +4,48 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.KmPackage;
+import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
 
-  static KotlinFile fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+  private KmPackage kmPackage;
+
+  static KotlinFile fromKotlinClassMetadata(
+      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.FileFacade;
     KotlinClassMetadata.FileFacade fileFacade =
         (KotlinClassMetadata.FileFacade) kotlinClassMetadata;
-    return new KotlinFile(fileFacade);
+    return new KotlinFile(fileFacade, clazz);
   }
 
-  private KotlinFile(KotlinClassMetadata.FileFacade metadata) {
-    super(metadata);
+  private KotlinFile(KotlinClassMetadata.FileFacade metadata, DexClass clazz) {
+    super(metadata, clazz);
   }
 
   @Override
   void processMetadata() {
     assert !isProcessed;
     isProcessed = true;
-    // TODO(b/70169921): once migration is complete, use #toKmPackage and store a mutable model.
+    kmPackage = metadata.toKmPackage();
+  }
+
+  @Override
+  void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    // TODO(b/70169921): no idea yet!
+    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+        : toString();
+  }
+
+  @Override
+  KotlinClassHeader createHeader() {
+    // TODO(b/70169921): may need to update if `rewrite` is implemented.
+    return metadata.getHeader();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 6603357..ac69eff 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 // Provides access to kotlin information.
@@ -20,11 +24,17 @@
   KotlinInfo(MetadataKind metadata, DexClass clazz) {
     this.metadata = metadata;
     this.clazz = clazz;
+    processMetadata();
   }
 
   // Subtypes will define how to process the given metadata.
   abstract void processMetadata();
 
+  // Subtypes will define how to rewrite metadata after shrinking and minification.
+  abstract void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens);
+
+  abstract KotlinClassHeader createHeader();
+
   public enum Kind {
     Class, File, Synthetic, Part, Facade
   }
@@ -70,4 +80,9 @@
   public KotlinClassFacade asClassFacade() {
     return null;
   }
+
+  @Override
+  public String toString() {
+    return clazz.toSourceString() + ": " + metadata.toString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
new file mode 100644
index 0000000..054c5e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -0,0 +1,142 @@
+// 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.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+
+public class KotlinMetadataRewriter {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final NamingLens lens;
+  private final DexItemFactory factory;
+  private final Kotlin kotlin;
+
+  public KotlinMetadataRewriter(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    this.appView = appView;
+    this.lens = lens;
+    this.factory = appView.dexItemFactory();
+    this.kotlin = factory.kotlin;
+  }
+
+  public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexType type) {
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz == null) {
+      return;
+    }
+    removeKotlinMetadataFromRenamedClass(appView, clazz);
+  }
+
+  public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexClass clazz) {
+    // Remove @Metadata in DexAnnotation form if a class is renamed.
+    clazz.annotations = clazz.annotations.keepIf(anno -> isNotKotlinMetadata(appView, anno));
+    // Clear associated {@link KotlinInfo} to avoid accidentally deserialize it back to
+    // DexAnnotation we've just removed above.
+    if (clazz.isProgramClass()) {
+      clazz.asProgramClass().setKotlinInfo(null);
+    }
+  }
+
+  private static boolean isNotKotlinMetadata(AppView<?> appView, DexAnnotation annotation) {
+    return annotation.annotation.type
+        != appView.dexItemFactory().kotlin.metadata.kotlinMetadataType;
+  }
+
+  public void run(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          KotlinInfo<?> kotlinInfo = clazz.getKotlinInfo();
+          if (kotlinInfo != null) {
+            assert kotlinInfo.isClass()
+                || kotlinInfo.isSyntheticClass()
+                || kotlinInfo.isFile(); // e.g., B.kt becomes class `BKt`
+            // If @Metadata is still associated, this class should not be renamed
+            // (by {@link ClassNameMinifier} of course).
+            assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+                : clazz.toSourceString() + " != "
+                    + lens.lookupType(clazz.type, appView.dexItemFactory());
+
+            DexAnnotation oldMeta =
+                clazz.annotations.getFirstMatching(kotlin.metadata.kotlinMetadataType);
+            // If @Metadata is already gone, e.g., by {@link AnnotationRemover} if type Metadata is
+            // determined as dead (e.g., due to no keep rule), nothing to do.
+            if (oldMeta == null) {
+              return;
+            }
+
+            kotlinInfo.rewrite(appView, lens);
+
+            DexAnnotation newMeta = createKotlinMetadataAnnotation(kotlinInfo.createHeader());
+            clazz.annotations = clazz.annotations.rewrite(anno -> anno == oldMeta ? newMeta : anno);
+          }
+        },
+        executorService
+    );
+  }
+
+  private DexAnnotation createKotlinMetadataAnnotation(KotlinClassHeader header) {
+    List<DexAnnotationElement> elements = new ArrayList<>();
+    elements.add(
+        new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
+    elements.add(
+        new DexAnnotationElement(
+            kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
+    elements.add(
+        new DexAnnotationElement(
+            kotlin.metadata.bytecodeVersion, createIntArray(header.getBytecodeVersion())));
+    elements.add(
+        new DexAnnotationElement(kotlin.metadata.data1, createStringArray(header.getData1())));
+    elements.add(
+        new DexAnnotationElement(kotlin.metadata.data2, createStringArray(header.getData2())));
+    elements.add(
+        new DexAnnotationElement(
+            kotlin.metadata.extraString,
+            new DexValueString(factory.createString(header.getExtraString()))));
+    elements.add(
+        new DexAnnotationElement(
+            kotlin.metadata.packageName,
+            new DexValueString(factory.createString(header.getPackageName()))));
+    elements.add(
+        new DexAnnotationElement(
+            kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
+    DexEncodedAnnotation encodedAnnotation =
+        new DexEncodedAnnotation(
+            kotlin.metadata.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
+    return new DexAnnotation(DexAnnotation.VISIBILITY_RUNTIME, encodedAnnotation);
+  }
+
+  private DexValueArray createIntArray(int[] data) {
+    DexValue[] values = new DexValue[data.length];
+    for (int i = 0; i < data.length; i++) {
+      values[i] = DexValueInt.create(data[i]);
+    }
+    return new DexValueArray(values);
+  }
+
+  private DexValueArray createStringArray(String[] data) {
+    DexValue[] values = new DexValue[data.length];
+    for (int i = 0; i < data.length; i++) {
+      values[i] = new DexValueString(factory.createString(data[i]));
+    }
+    return new DexValueArray(values);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index fa65464..59f7dce 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
@@ -22,16 +26,17 @@
     KotlinClassMetadata.SyntheticClass syntheticClass =
         (KotlinClassMetadata.SyntheticClass) kotlinClassMetadata;
     if (isKotlinStyleLambda(syntheticClass, kotlin, clazz)) {
-      return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass);
+      return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass, clazz);
     } else if (isJavaStyleLambda(syntheticClass, kotlin, clazz)) {
-      return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass);
+      return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass, clazz);
     } else {
-      return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass);
+      return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass, clazz);
     }
   }
 
-  private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) {
-    super(metadata);
+  private KotlinSyntheticClass(
+      Flavour flavour, KotlinClassMetadata.SyntheticClass metadata, DexClass clazz) {
+    super(metadata, clazz);
     this.flavour = flavour;
   }
 
@@ -40,10 +45,23 @@
     assert !isProcessed;
     isProcessed = true;
     if (metadata.isLambda()) {
-      // TODO(b/70169921): once migration is complete, use #toKmLambda and store a mutable model.
+      // TODO(b/70169921): Use #toKmLambda to store a mutable model if needed.
     }
   }
 
+  @Override
+  void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    // TODO(b/70169921): no idea yet!
+    assert lens.lookupType(clazz.type, appView.dexItemFactory()) == clazz.type
+        : toString();
+  }
+
+  @Override
+  KotlinClassHeader createHeader() {
+    // TODO(b/70169921): may need to update if `rewrite` is implemented.
+    return metadata.getHeader();
+  }
+
   public boolean isLambda() {
     return isKotlinStyleLambda() || isJavaStyleLambda();
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index ada50fa..d0240e9 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -10,7 +10,6 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getPackageBinaryNameFromJavaType;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,6 +17,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardPackageNameList;
@@ -110,16 +110,17 @@
     timing.begin("rename-classes");
     for (DexClass clazz : classes) {
       if (!renaming.containsKey(clazz.type)) {
-        clazz.annotations = clazz.annotations.keepIf(this::isNotKotlinMetadata);
         DexString renamed = computeName(clazz.type);
         renaming.put(clazz.type, renamed);
+        KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
         // If the class is a member class and it has used $ separator, its renamed name should have
         // the same separator (as long as inner-class attribute is honored).
         assert !keepInnerClassStructure
             || !clazz.isMemberClass()
             || !clazz.type.getInternalName().contains(String.valueOf(INNER_CLASS_SEPARATOR))
             || renamed.toString().contains(String.valueOf(INNER_CLASS_SEPARATOR))
-            || classNamingStrategy.isRenamedByApplyMapping(clazz.type);
+            || classNamingStrategy.isRenamedByApplyMapping(clazz.type)
+                : clazz.toSourceString() + " -> " + renamed;
       }
     }
     timing.end();
@@ -327,6 +328,7 @@
         // and then use that renamed name as a base prefix for the current inner class.
         renamed = computeName(outer);
         renaming.put(outer, renamed);
+        KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, outer);
       }
       String binaryName = getClassBinaryNameFromDescriptor(renamed.toString());
       state = new Namespace(binaryName, innerClassSeparator);
@@ -442,9 +444,4 @@
     }
     return packagePrefix.substring(0, i);
   }
-
-  private boolean isNotKotlinMetadata(DexAnnotation annotation) {
-    return annotation.annotation.type
-        != appView.dexItemFactory().kotlin.metadata.kotlinMetadataType;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index ccac2d6..c6b7ee0 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -185,6 +185,7 @@
       if (isDirectMethodCall) {
         return virtualNameCount + directNameCount++;
       } else {
+        // TODO(b/144877828): is it guaranteed?
         assert directNameCount == 0;
         return virtualNameCount++;
       }
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 7423075..7d93488 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -98,10 +98,6 @@
     if (renamed != null) {
       return renamed;
     }
-    // TODO(b/144339115): Don't allocate in the item factory during resolution!
-    if (method.holder == appView.dexItemFactory().methodHandleType) {
-      return method.name;
-    }
     // If the method does not have a direct renaming, return the resolutions mapping.
     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
     if (resolutionResult.hasSingleTarget()) {
@@ -111,7 +107,7 @@
     // to the failure.
     if (resolutionResult.isFailedResolution()) {
       List<DexEncodedMethod> targets = new ArrayList<>();
-      resolutionResult.asFailedResolution().forEachFailureDependency(clazz -> {}, targets::add);
+      resolutionResult.asFailedResolution().forEachFailureDependency(targets::add);
       if (!targets.isEmpty()) {
         DexString firstRename = renaming.get(targets.get(0).method);
         assert targets.stream().allMatch(target -> renaming.get(target.method) == firstRename);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 8dfe02c..eb3a614 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.ClassNameMinifier.PackageNamingStrategy;
@@ -87,6 +88,11 @@
     timing.begin("MinifyIdentifiers");
     new IdentifierMinifier(appView, lens).run(executorService);
     timing.end();
+
+    timing.begin("MinifyKotlinMetadata");
+    new KotlinMetadataRewriter(appView, lens).run(executorService);
+    timing.end();
+
     return lens;
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 8db8e01..92df84f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -165,6 +166,10 @@
     new IdentifierMinifier(appView, lens).run(executorService);
     timing.end();
 
+    timing.begin("MinifyKotlinMetadata");
+    new KotlinMetadataRewriter(appView, lens).run(executorService);
+    timing.end();
+
     return lens;
   }
 
@@ -180,9 +185,12 @@
     Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
 
     if (classNaming != null) {
-      // TODO(b/133091438) assert that !dexClass.isLibaryClass();
+      // TODO(b/133091438) assert that !dexClass.isLibraryClass();
       DexString mappedName = factory.createString(classNaming.renamedName);
       checkAndAddMappedNames(type, mappedName, classNaming.position);
+      if (dexClass != null) {
+        KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, dexClass);
+      }
 
       classNaming.forAllMemberNaming(
           memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
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 760904a..703deea 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -107,15 +107,15 @@
   }
 
   private DexEncodedMethod classLookup(DexMethod method) {
-    return appView.appInfo().resolveMethodOnClass(method.holder, method).asResultOfResolve();
+    return appView.appInfo().resolveMethodOnClass(method.holder, method).getSingleTarget();
   }
 
   private DexEncodedMethod interfaceLookup(DexMethod method) {
-    return appView.appInfo().resolveMethodOnInterface(method.holder, method).asResultOfResolve();
+    return appView.appInfo().resolveMethodOnInterface(method.holder, method).getSingleTarget();
   }
 
   private DexEncodedMethod anyLookup(DexMethod method) {
-    return appView.appInfo().resolveMethod(method.holder, method).asResultOfResolve();
+    return appView.appInfo().resolveMethod(method.holder, method).getSingleTarget();
   }
 
   private void computeMethodRebinding(
diff --git a/src/main/java/com/android/tools/r8/references/ClassReference.java b/src/main/java/com/android/tools/r8/references/ClassReference.java
index 50ce220..1159ec3 100644
--- a/src/main/java/com/android/tools/r8/references/ClassReference.java
+++ b/src/main/java/com/android/tools/r8/references/ClassReference.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.references;
 
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.DescriptorUtils;
 
 /** Reference to a class type or interface type. */
 @Keep
@@ -19,6 +20,10 @@
     return new ClassReference(descriptor);
   }
 
+  public String getBinaryName() {
+    return DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+  }
+
   @Override
   public boolean isClass() {
     return true;
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 319cbaf..f5b0de8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -758,24 +758,20 @@
     return false;
   }
 
-  public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
-    assert checkIfObsolete();
-    assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
-    if (!isPinned(field.field)) {
-      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
-      return fieldAccessInfo != null
-          && fieldAccessInfo.isWritten()
-          && !fieldAccessInfo.isWrittenOutside(method);
-    }
-    return false;
-  }
-
   public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
-    DexEncodedMethod staticInitializer =
-        definitionFor(field.field.holder).asProgramClass().getClassInitializer();
-    return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
+    if (!isPinned(field.field)) {
+      DexEncodedMethod staticInitializer =
+          definitionFor(field.field.holder).asProgramClass().getClassInitializer();
+      if (staticInitializer != null) {
+        FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+        return fieldAccessInfo != null
+            && fieldAccessInfo.isWritten()
+            && !fieldAccessInfo.isWrittenOutside(staticInitializer);
+      }
+    }
+    return false;
   }
 
   public boolean mayPropagateValueFor(DexReference reference) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
new file mode 100644
index 0000000..773db00
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DelayedRootSetActionItem.java
@@ -0,0 +1,56 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import java.util.function.Consumer;
+
+public abstract class DelayedRootSetActionItem {
+
+  public boolean isInterfaceMethodSyntheticBridgeAction() {
+    return false;
+  }
+
+  public InterfaceMethodSyntheticBridgeAction asInterfaceMethodSyntheticBridgeAction() {
+    return null;
+  }
+
+  public static class InterfaceMethodSyntheticBridgeAction extends DelayedRootSetActionItem {
+    private final DexEncodedMethod methodToKeep;
+    private final DexEncodedMethod singleTarget;
+    private final Consumer<RootSetBuilder> action;
+
+    InterfaceMethodSyntheticBridgeAction(
+        DexEncodedMethod methodToKeep,
+        DexEncodedMethod singleTarget,
+        Consumer<RootSetBuilder> action) {
+      this.methodToKeep = methodToKeep;
+      this.singleTarget = singleTarget;
+      this.action = action;
+    }
+
+    public DexEncodedMethod getMethodToKeep() {
+      return methodToKeep;
+    }
+
+    public DexEncodedMethod getSingleTarget() {
+      return singleTarget;
+    }
+
+    public Consumer<RootSetBuilder> getAction() {
+      return action;
+    }
+
+    @Override
+    public boolean isInterfaceMethodSyntheticBridgeAction() {
+      return true;
+    }
+
+    @Override
+    public InterfaceMethodSyntheticBridgeAction asInterfaceMethodSyntheticBridgeAction() {
+      return 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 24108b7..f7cf47c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -11,7 +11,6 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -57,11 +56,13 @@
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.EnqueuerWorklist.Action;
+import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
+import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
@@ -76,6 +77,7 @@
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.lang.reflect.InvocationHandler;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
@@ -743,10 +745,35 @@
 
   boolean traceInvokeDirect(
       DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+    boolean skipTracing =
+        registerDeferredActionForDeadProtoBuilder(
+            invokedMethod.holder,
+            currentMethod,
+            () ->
+                workList.enqueueTraceInvokeDirectAction(
+                    invokedMethod, currentHolder, currentMethod));
+    if (skipTracing) {
+      return false;
+    }
+
     return traceInvokeDirect(
         invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
   }
 
+  /** Returns true if a deferred action was registered. */
+  private boolean registerDeferredActionForDeadProtoBuilder(
+      DexType type, DexEncodedMethod currentMethod, Action action) {
+    DexProgramClass clazz = getProgramClassOrNull(type);
+    if (clazz != null) {
+      return appView.withGeneratedMessageLiteBuilderShrinker(
+          shrinker ->
+              shrinker.deferDeadProtoBuilders(
+                  clazz, currentMethod, () -> liveTypes.registerDeferredAction(clazz, action)),
+          false);
+    }
+    return false;
+  }
+
   boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
     return traceInvokeDirect(
         invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
@@ -877,6 +904,13 @@
   }
 
   boolean traceNewInstance(DexType type, DexEncodedMethod currentMethod) {
+    boolean skipTracing =
+        registerDeferredActionForDeadProtoBuilder(
+            type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, currentMethod));
+    if (skipTracing) {
+      return false;
+    }
+
     return traceNewInstance(type, currentMethod, KeepReason.instantiatedIn(currentMethod));
   }
 
@@ -1626,22 +1660,18 @@
 
   private void markResolutionAsLive(DexClass libraryClass, ResolutionResult resolution) {
     if (resolution.isValidVirtualTarget(options)) {
-      resolution.forEachTarget(
-          target -> {
-            if (!target.isAbstract()) {
-              DexClass targetHolder = appView.definitionFor(target.method.holder);
-              if (targetHolder != null && targetHolder.isProgramClass()) {
-                DexProgramClass programClass = targetHolder.asProgramClass();
-                if (shouldMarkLibraryMethodOverrideAsReachable(programClass, target)) {
-                  target.setLibraryMethodOverride();
-                  markVirtualMethodAsLive(
-                      programClass,
-                      target,
-                      KeepReason.isLibraryMethod(programClass, libraryClass.type));
-                }
-              }
-            }
-          });
+      DexEncodedMethod target = resolution.getSingleTarget();
+      if (!target.isAbstract()) {
+        DexClass targetHolder = appView.definitionFor(target.method.holder);
+        if (targetHolder != null && targetHolder.isProgramClass()) {
+          DexProgramClass programClass = targetHolder.asProgramClass();
+          if (shouldMarkLibraryMethodOverrideAsReachable(programClass, target)) {
+            target.setLibraryMethodOverride();
+            markVirtualMethodAsLive(
+                programClass, target, KeepReason.isLibraryMethod(programClass, libraryClass.type));
+          }
+        }
+      }
     }
   }
 
@@ -2077,9 +2107,6 @@
   private void markFailedResolutionTargets(
       FailedResolutionResult failedResolution, KeepReason reason) {
     failedResolution.forEachFailureDependency(
-        clazz -> {
-          throw new Unimplemented();
-        },
         method -> {
           DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
           if (clazz != null) {
@@ -2119,8 +2146,8 @@
     // See <a
     // href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial">
     // the JVM spec for invoke-special.
-    DexEncodedMethod resolutionTarget = appInfo.resolveMethod(method.holder, method)
-        .asResultOfResolve();
+    DexEncodedMethod resolutionTarget =
+        appInfo.resolveMethod(method.holder, method).getSingleTarget();
     if (resolutionTarget == null) {
       brokenSuperInvokes.add(method);
       reportMissingMethod(method);
@@ -2295,7 +2322,7 @@
         numOfLiveItems += (long) liveMethods.items.size();
         numOfLiveItems += (long) liveFields.items.size();
         while (!workList.isEmpty()) {
-          Action action = workList.poll();
+          EnqueuerAction action = workList.poll();
           action.run(this);
         }
 
@@ -2314,7 +2341,7 @@
               activeIfRules.computeIfAbsent(wrap, ignore -> new LinkedHashSet<>()).add(ifRule);
             }
           }
-          RootSetBuilder consequentSetBuilder = new RootSetBuilder(appView, null);
+          RootSetBuilder consequentSetBuilder = new RootSetBuilder(appView);
           IfRuleEvaluator ifRuleEvaluator =
               new IfRuleEvaluator(
                   appView,
@@ -2326,22 +2353,7 @@
                   mode,
                   consequentSetBuilder,
                   targetedMethods.getItems());
-          ConsequentRootSet consequentRootSet = ifRuleEvaluator.run();
-          // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
-          rootSet.addConsequentRootSet(consequentRootSet);
-          enqueueRootItems(consequentRootSet.noShrinking);
-          // TODO(b/132828740): Seems incorrect that the precondition is not always met here.
-          consequentRootSet.dependentNoShrinking.forEach(
-              (precondition, dependentItems) -> enqueueRootItems(dependentItems));
-          // Check for compatibility rules indicating that the holder must be implicitly kept.
-          if (forceProguardCompatibility) {
-            consequentRootSet.dependentKeepClassCompatRule.forEach(
-                (precondition, compatRules) -> {
-                  assert precondition.isDexType();
-                  DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
-                  compatEnqueueHolderIfDependentNonStaticMember(preconditionHolder, compatRules);
-                });
-          }
+          addConsequentRootSet(ifRuleEvaluator.run(), false);
           if (!workList.isEmpty()) {
             continue;
           }
@@ -2364,6 +2376,13 @@
           continue;
         }
 
+        addConsequentRootSet(computeDelayedInterfaceMethodSyntheticBridges(), true);
+        rootSet.delayedRootSetActionItems.clear();
+
+        if (!workList.isEmpty()) {
+          continue;
+        }
+
         // Reached the fixpoint.
         break;
       }
@@ -2392,6 +2411,55 @@
     unpinLambdaMethods();
   }
 
+  private void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
+    // TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
+    rootSet.addConsequentRootSet(consequentRootSet, addNoShrinking);
+    enqueueRootItems(consequentRootSet.noShrinking);
+    // TODO(b/132828740): Seems incorrect that the precondition is not always met here.
+    consequentRootSet.dependentNoShrinking.forEach(
+        (precondition, dependentItems) -> enqueueRootItems(dependentItems));
+    // Check for compatibility rules indicating that the holder must be implicitly kept.
+    if (forceProguardCompatibility) {
+      consequentRootSet.dependentKeepClassCompatRule.forEach(
+          (precondition, compatRules) -> {
+            assert precondition.isDexType();
+            DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
+            compatEnqueueHolderIfDependentNonStaticMember(preconditionHolder, compatRules);
+          });
+    }
+  }
+
+  private ConsequentRootSet computeDelayedInterfaceMethodSyntheticBridges() {
+    RootSetBuilder builder = new RootSetBuilder(appView);
+    for (DelayedRootSetActionItem delayedRootSetActionItem : rootSet.delayedRootSetActionItems) {
+      if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) {
+        handleInterfaceMethodSyntheticBridgeAction(
+            delayedRootSetActionItem.asInterfaceMethodSyntheticBridgeAction(), builder);
+      }
+    }
+    return builder.buildConsequentRootSet();
+  }
+
+  private void handleInterfaceMethodSyntheticBridgeAction(
+      InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
+    if (rootSet.noShrinking.containsKey(action.getSingleTarget().method)) {
+      return;
+    }
+    DexEncodedMethod methodToKeep = action.getMethodToKeep();
+    DexClass clazz = getProgramClassOrNull(methodToKeep.method.holder);
+    if (methodToKeep != action.getSingleTarget()) {
+      // Insert a bridge method.
+      if (appView.definitionFor(methodToKeep.method) == null) {
+        clazz.appendVirtualMethod(methodToKeep);
+        appView.appInfo().invalidateTypeCacheFor(methodToKeep.method.holder);
+        // The addition of a bridge method can lead to a change of resolution, thus the cached
+        // resolution targets are invalid.
+        virtualTargetsMarkedAsReachable.remove(methodToKeep.method);
+      }
+    }
+    action.getAction().accept(builder);
+  }
+
   private void unpinLambdaMethods() {
     for (DexMethod method : lambdaMethodsTargetedByInvokeDynamic) {
       pinnedItems.remove(method);
@@ -2911,16 +2979,29 @@
   private static class SetWithReportedReason<T> {
 
     private final Set<T> items = Sets.newIdentityHashSet();
+    private final Map<T, List<Action>> deferredActions = new IdentityHashMap<>();
 
     boolean add(T item, KeepReasonWitness witness) {
       assert witness != null;
-      return items.add(item);
+      if (items.add(item)) {
+        deferredActions.getOrDefault(item, Collections.emptyList()).forEach(Action::execute);
+        return true;
+      }
+      return false;
     }
 
     boolean contains(T item) {
       return items.contains(item);
     }
 
+    boolean registerDeferredAction(T item, Action action) {
+      if (!items.contains(item)) {
+        deferredActions.computeIfAbsent(item, ignore -> new ArrayList<>()).add(action);
+        return true;
+      }
+      return false;
+    }
+
     Set<T> getItems() {
       return Collections.unmodifiableSet(items);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 5b3be60..ff6ece0 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -17,11 +17,11 @@
 
 public class EnqueuerWorklist {
 
-  public abstract static class Action {
+  public abstract static class EnqueuerAction {
     public abstract void run(Enqueuer enqueuer);
   }
 
-  static class MarkReachableDirectAction extends Action {
+  static class MarkReachableDirectAction extends EnqueuerAction {
     final DexMethod target;
     final KeepReason reason;
 
@@ -36,7 +36,7 @@
     }
   }
 
-  static class MarkReachableVirtualAction extends Action {
+  static class MarkReachableVirtualAction extends EnqueuerAction {
     final DexMethod target;
     final KeepReason reason;
 
@@ -51,7 +51,7 @@
     }
   }
 
-  static class MarkReachableInterfaceAction extends Action {
+  static class MarkReachableInterfaceAction extends EnqueuerAction {
     final DexMethod target;
     final KeepReason reason;
 
@@ -66,7 +66,7 @@
     }
   }
 
-  static class MarkReachableSuperAction extends Action {
+  static class MarkReachableSuperAction extends EnqueuerAction {
     final DexMethod target;
     final DexEncodedMethod context;
 
@@ -81,7 +81,7 @@
     }
   }
 
-  static class MarkReachableFieldAction extends Action {
+  static class MarkReachableFieldAction extends EnqueuerAction {
     final DexEncodedField target;
     final KeepReason reason;
 
@@ -96,7 +96,7 @@
     }
   }
 
-  static class MarkInstantiatedAction extends Action {
+  static class MarkInstantiatedAction extends EnqueuerAction {
     final DexProgramClass target;
     final DexEncodedMethod context;
     final KeepReason reason;
@@ -114,7 +114,7 @@
     }
   }
 
-  static class MarkMethodLiveAction extends Action {
+  static class MarkMethodLiveAction extends EnqueuerAction {
     final DexEncodedMethod target;
     final KeepReason reason;
 
@@ -129,7 +129,7 @@
     }
   }
 
-  static class MarkMethodKeptAction extends Action {
+  static class MarkMethodKeptAction extends EnqueuerAction {
     final DexProgramClass holder;
     final DexEncodedMethod target;
     final KeepReason reason;
@@ -147,7 +147,7 @@
     }
   }
 
-  static class MarkFieldKeptAction extends Action {
+  static class MarkFieldKeptAction extends EnqueuerAction {
     final DexProgramClass holder;
     final DexEncodedField target;
     final KeepReasonWitness witness;
@@ -165,7 +165,7 @@
     }
   }
 
-  static class TraceConstClassAction extends Action {
+  static class TraceConstClassAction extends EnqueuerAction {
     final DexType type;
     final DexEncodedMethod currentMethod;
 
@@ -180,7 +180,40 @@
     }
   }
 
-  static class TraceStaticFieldReadAction extends Action {
+  static class TraceInvokeDirectAction extends EnqueuerAction {
+    final DexMethod invokedMethod;
+    final DexProgramClass currentHolder;
+    final DexEncodedMethod currentMethod;
+
+    TraceInvokeDirectAction(
+        DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+      this.invokedMethod = invokedMethod;
+      this.currentHolder = currentHolder;
+      this.currentMethod = currentMethod;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceInvokeDirect(invokedMethod, currentHolder, currentMethod);
+    }
+  }
+
+  static class TraceNewInstanceAction extends EnqueuerAction {
+    final DexType type;
+    final DexEncodedMethod currentMethod;
+
+    TraceNewInstanceAction(DexType type, DexEncodedMethod currentMethod) {
+      this.type = type;
+      this.currentMethod = currentMethod;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceNewInstance(type, currentMethod);
+    }
+  }
+
+  static class TraceStaticFieldReadAction extends EnqueuerAction {
     final DexField field;
     final DexEncodedMethod currentMethod;
 
@@ -196,7 +229,7 @@
   }
 
   private final AppView<?> appView;
-  private final Queue<Action> queue = new ArrayDeque<>();
+  private final Queue<EnqueuerAction> queue = new ArrayDeque<>();
 
   private EnqueuerWorklist(AppView<?> appView) {
     this.appView = appView;
@@ -210,7 +243,7 @@
     return queue.isEmpty();
   }
 
-  public Action poll() {
+  public EnqueuerAction poll() {
     return queue.poll();
   }
 
@@ -267,6 +300,17 @@
     queue.add(new TraceConstClassAction(type, currentMethod));
   }
 
+  public void enqueueTraceInvokeDirectAction(
+      DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+    assert currentMethod.method.holder == currentHolder.type;
+    queue.add(new TraceInvokeDirectAction(invokedMethod, currentHolder, currentMethod));
+  }
+
+  public void enqueueTraceNewInstanceAction(DexType type, DexEncodedMethod currentMethod) {
+    assert currentMethod.isProgramMethod(appView);
+    queue.add(new TraceNewInstanceAction(type, currentMethod));
+  }
+
   public void enqueueTraceStaticFieldRead(DexField field, DexEncodedMethod currentMethod) {
     assert currentMethod.isProgramMethod(appView);
     queue.add(new TraceStaticFieldReadAction(field, currentMethod));
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 77d0b58..ed49a01 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -49,6 +49,14 @@
     used = true;
   }
 
+  public boolean isProguardKeepRule() {
+    return false;
+  }
+
+  public ProguardKeepRule asProguardKeepRule() {
+    return null;
+  }
+
   Iterable<DexProgramClass> relevantCandidatesForRule(
       AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> defaultValue) {
     if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 5e382f9..4633882 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -127,4 +127,14 @@
     modifiers.accept(builder.getModifiersBuilder());
     return builder.build();
   }
+
+  @Override
+  public boolean isProguardKeepRule() {
+    return true;
+  }
+
+  @Override
+  public ProguardKeepRule asProguardKeepRule() {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 55b880b..fee13db 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -25,7 +24,11 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
+import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.Consumer3;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -35,6 +38,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.io.PrintStream;
@@ -51,12 +55,13 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Queue;
 import java.util.Set;
 import java.util.Stack;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
-import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -73,6 +78,7 @@
   private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+  private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
@@ -87,6 +93,8 @@
   private final Map<DexReference, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
   private final Map<DexReference, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
   private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+  private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
+      new ConcurrentLinkedQueue<>();
   private final InternalOptions options;
 
   private final DexStringCache dexStringCache = new DexStringCache();
@@ -102,11 +110,15 @@
     this.options = appView.options();
   }
 
-  RootSetBuilder(
+  public RootSetBuilder(
       AppView<? extends AppInfoWithSubtyping> appView, Collection<ProguardIfRule> ifRules) {
     this(appView, appView.appInfo().app(), ifRules);
   }
 
+  public RootSetBuilder(AppView<? extends AppInfoWithSubtyping> appView) {
+    this(appView, appView.appInfo().app(), null);
+  }
+
   // Process a class with the keep rule.
   private void process(
       DexClass clazz,
@@ -280,6 +292,10 @@
       BottomUpClassHierarchyTraversal.forAllClasses(appView)
           .visit(appView.appInfo().classes(), this::propagateAssumeRules);
     }
+    if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
+      GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
+          appView, alwaysInline, neverInline, bypassClinitforInlining);
+    }
     assert Sets.intersection(neverInline, alwaysInline).isEmpty()
             && Sets.intersection(neverInline, forceInline).isEmpty()
         : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
@@ -292,6 +308,7 @@
         alwaysInline,
         forceInline,
         neverInline,
+        bypassClinitforInlining,
         whyAreYouNotInlining,
         keepParametersWithConstantValue,
         keepUnusedArguments,
@@ -304,7 +321,8 @@
         dependentNoShrinking,
         dependentKeepClassCompatRule,
         identifierNameStrings,
-        ifRules);
+        ifRules,
+        Lists.newArrayList(delayedRootSetActionItems));
   }
 
   private void propagateAssumeRules(DexClass clazz) {
@@ -337,7 +355,7 @@
       // is due to the lack of the definition or it indeed means no matching rules. Similar to how
       // we apply those assume rules, here we use a resolved target.
       DexEncodedMethod target =
-          appView.appInfo().resolveMethod(subType, referenceInSubType).asResultOfResolve();
+          appView.appInfo().resolveMethod(subType, referenceInSubType).getSingleTarget();
       // But, the resolution should not be landed on the current type we are visiting.
       if (target == null || target.method.holder == type) {
         continue;
@@ -372,7 +390,8 @@
         noOptimization,
         noObfuscation,
         dependentNoShrinking,
-        dependentKeepClassCompatRule);
+        dependentKeepClassCompatRule,
+        Lists.newArrayList(delayedRootSetActionItems));
   }
 
   private static DexDefinition testAndGetPrecondition(
@@ -409,7 +428,7 @@
     while (!worklist.isEmpty()) {
       DexClass currentClass = worklist.pop();
       if (!includeLibraryClasses && currentClass.isNotProgramClass()) {
-        return;
+        break;
       }
       // In compat mode traverse all direct methods in the hierarchy.
       if (currentClass == clazz || options.forceProguardCompatibility) {
@@ -434,15 +453,128 @@
           worklist.add(dexClass);
         }
       }
-      if (options.testing.keepInheritedInterfaceMethods) {
-        for (DexType iface : currentClass.interfaces.values) {
-          DexClass interfaceClass = application.definitionFor(iface);
-          if (interfaceClass != null) {
-            worklist.add(interfaceClass);
+    }
+    // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in
+    //  fullmode.
+    if (clazz.isProgramClass()) {
+      new SynthesizeMissingInterfaceMethodsForMemberRules(
+              clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule)
+          .run();
+    }
+  }
+
+  /**
+   * Utility class for visiting all super interfaces to ensure we keep method definitions specified
+   * by proguard rules. If possible, we generate a forwarding bridge to the resolved target. If not,
+   * we specifically synthesize a keep rule for the interface method.
+   */
+  private class SynthesizeMissingInterfaceMethodsForMemberRules {
+    private final DexProgramClass originalClazz;
+    private final Collection<ProguardMemberRule> memberKeepRules;
+    private final ProguardConfigurationRule context;
+    private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
+    private final ProguardIfRule ifRule;
+    private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet();
+    private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
+
+    private SynthesizeMissingInterfaceMethodsForMemberRules(
+        DexProgramClass originalClazz,
+        Collection<ProguardMemberRule> memberKeepRules,
+        ProguardConfigurationRule context,
+        Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+        ProguardIfRule ifRule) {
+      this.originalClazz = originalClazz;
+      this.memberKeepRules = memberKeepRules;
+      this.context = context;
+      this.preconditionSupplier = preconditionSupplier;
+      this.ifRule = ifRule;
+    }
+
+    void run() {
+      visitAllSuperInterfaces(originalClazz.type);
+    }
+
+    private void visitAllSuperInterfaces(DexType type) {
+      DexClass clazz = appView.definitionFor(type);
+      if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) {
+        return;
+      }
+      for (DexType iface : clazz.interfaces.values) {
+        visitAllSuperInterfaces(iface);
+      }
+      if (!clazz.isInterface()) {
+        visitAllSuperInterfaces(clazz.superType);
+        return;
+      }
+      if (originalClazz == clazz) {
+        return;
+      }
+      for (DexEncodedMethod method : clazz.virtualMethods()) {
+        // Check if we already added this.
+        Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.method);
+        if (!seenMethods.add(wrapped)) {
+          continue;
+        }
+        for (ProguardMemberRule rule : memberKeepRules) {
+          if (rule.matches(method, appView, dexStringCache)) {
+            tryAndKeepMethodOnClass(method, rule);
           }
         }
       }
     }
+
+    private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
+      boolean shouldKeepMethod =
+          context.isProguardKeepRule()
+              && !context.asProguardKeepRule().getModifiers().allowsShrinking;
+      if (!shouldKeepMethod) {
+        return;
+      }
+      ResolutionResult resolutionResult =
+          appView.appInfo().resolveMethod(originalClazz, method.method);
+      if (!resolutionResult.isValidVirtualTarget(appView.options())
+          || !resolutionResult.hasSingleTarget()) {
+        return;
+      }
+      DexEncodedMethod methodToKeep = resolutionResult.getSingleTarget();
+      if (methodToKeep.method.holder == originalClazz.type) {
+        return;
+      }
+      DexClass holder = appView.definitionFor(methodToKeep.method.holder);
+      if (holder.isNotProgramClass()) {
+        return;
+      }
+      if (!holder.isInterface()) {
+        // TODO(b/143643942): For fullmode, this check should probably be removed.
+        return;
+      }
+      if (canInsertForwardingMethod(originalClazz, methodToKeep)) {
+        methodToKeep = methodToKeep.toForwardingMethod(originalClazz, appView);
+      }
+      final DexEncodedMethod finalKeepMethod = methodToKeep;
+      delayedRootSetActionItems.add(
+          new InterfaceMethodSyntheticBridgeAction(
+              methodToKeep,
+              resolutionResult.getSingleTarget(),
+              (rootSetBuilder) -> {
+                if (Log.ENABLED) {
+                  Log.verbose(
+                      getClass(),
+                      "Marking method `%s` due to `%s { %s }`.",
+                      finalKeepMethod,
+                      context,
+                      rule);
+                }
+                DexDefinition precondition =
+                    testAndGetPrecondition(finalKeepMethod, preconditionSupplier);
+                rootSetBuilder.addItemToSets(finalKeepMethod, context, rule, precondition, ifRule);
+              }));
+    }
+  }
+
+  private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
+    return appView.options().isGeneratingDex()
+        || ArrayUtils.contains(holder.interfaces.values, target.method.holder);
   }
 
   private void markMatchingOverriddenMethods(
@@ -1053,6 +1185,7 @@
     public final Set<DexMethod> alwaysInline;
     public final Set<DexMethod> forceInline;
     public final Set<DexMethod> neverInline;
+    public final Set<DexMethod> bypassClinitForInlining;
     public final Set<DexMethod> whyAreYouNotInlining;
     public final Set<DexMethod> keepConstantArguments;
     public final Set<DexMethod> keepUnusedArguments;
@@ -1067,6 +1200,7 @@
     private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
     public final Set<DexReference> identifierNameStrings;
     public final Set<ProguardIfRule> ifRules;
+    public final List<DelayedRootSetActionItem> delayedRootSetActionItems;
 
     private RootSet(
         Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
@@ -1077,6 +1211,7 @@
         Set<DexMethod> alwaysInline,
         Set<DexMethod> forceInline,
         Set<DexMethod> neverInline,
+        Set<DexMethod> bypassClinitForInlining,
         Set<DexMethod> whyAreYouNotInlining,
         Set<DexMethod> keepConstantArguments,
         Set<DexMethod> keepUnusedArguments,
@@ -1089,7 +1224,8 @@
         Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         Set<DexReference> identifierNameStrings,
-        Set<ProguardIfRule> ifRules) {
+        Set<ProguardIfRule> ifRules,
+        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.noShrinking = noShrinking;
       this.noOptimization = noOptimization;
       this.noObfuscation = noObfuscation;
@@ -1098,6 +1234,7 @@
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
       this.forceInline = Collections.unmodifiableSet(forceInline);
       this.neverInline = neverInline;
+      this.bypassClinitForInlining = bypassClinitForInlining;
       this.whyAreYouNotInlining = whyAreYouNotInlining;
       this.keepConstantArguments = keepConstantArguments;
       this.keepUnusedArguments = keepUnusedArguments;
@@ -1111,6 +1248,7 @@
       this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
       this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
       this.ifRules = Collections.unmodifiableSet(ifRules);
+      this.delayedRootSetActionItems = delayedRootSetActionItems;
     }
 
     public void checkAllRulesAreUsed(InternalOptions options) {
@@ -1132,25 +1270,21 @@
       }
     }
 
-    private static <T extends DexReference, S> Map<T, Map<T, S>> rewriteDependentReferenceKeys(
-        Map<T, Map<T, S>> original, Function<T, T> rewrite) {
-      Map<T, Map<T, S>> result = new IdentityHashMap<>();
-      for (T item : original.keySet()) {
-        result.put(rewrite.apply(item), rewriteReferenceKeys(original.get(item), rewrite));
-      }
-      return result;
-    }
-
-    void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
+    void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
       neverInline.addAll(consequentRootSet.neverInline);
       neverClassInline.addAll(consequentRootSet.neverClassInline);
       noOptimization.addAll(consequentRootSet.noOptimization);
       noObfuscation.addAll(consequentRootSet.noObfuscation);
+      if (addNoShrinking) {
+        consequentRootSet.noShrinking.forEach(
+            (type, rules) -> noShrinking.computeIfAbsent(type, k -> new HashSet<>()).addAll(rules));
+      }
       addDependentItems(consequentRootSet.dependentNoShrinking);
       consequentRootSet.dependentKeepClassCompatRule.forEach(
           (type, rules) ->
               dependentKeepClassCompatRule.computeIfAbsent(
                   type, k -> new HashSet<>()).addAll(rules));
+      delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems);
     }
 
     // Add dependent items that depend on -if rules.
@@ -1422,8 +1556,9 @@
     }
   }
 
-  // A partial RootSet that becomes live due to the enabled -if rule.
-  static class ConsequentRootSet {
+  // A partial RootSet that becomes live due to the enabled -if rule or the addition of interface
+  // keep rules.
+  public static class ConsequentRootSet {
     final Set<DexMethod> neverInline;
     final Set<DexType> neverClassInline;
     final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
@@ -1431,6 +1566,7 @@
     final Set<DexReference> noObfuscation;
     final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
+    final List<DelayedRootSetActionItem> delayedRootSetActionItems;
 
     private ConsequentRootSet(
         Set<DexMethod> neverInline,
@@ -1439,7 +1575,8 @@
         Set<DexReference> noOptimization,
         Set<DexReference> noObfuscation,
         Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
-        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule) {
+        Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
+        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.neverInline = Collections.unmodifiableSet(neverInline);
       this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
@@ -1447,6 +1584,7 @@
       this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
       this.dependentNoShrinking = Collections.unmodifiableMap(dependentNoShrinking);
       this.dependentKeepClassCompatRule = Collections.unmodifiableMap(dependentKeepClassCompatRule);
+      this.delayedRootSetActionItems = Collections.unmodifiableList(delayedRootSetActionItems);
     }
   }
 }
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 a9ec234..f07499b 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -327,6 +327,11 @@
     for (DexMethod signature : appInfo.virtualMethodsTargetedByInvokeDirect) {
       markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_DIRECT);
     }
+
+    // The set of targets that must remain for proper resolution error cases should not be merged.
+    for (DexMethod method : appInfo.targetedMethodsThatMustRemainNonAbstract) {
+      markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
+    }
   }
 
   private <T extends DexReference> void extractPinnedItems(Iterable<T> items, AbortReason reason) {
@@ -989,15 +994,15 @@
           // Non-abstract methods are handled below (they cannot simply be moved to the subclass as
           // a virtual method, because they might be the target of an invoke-super instruction).
           if (virtualMethod.accessFlags.isAbstract()) {
+            // Abort if target is non-abstract and does not override the abstract method.
+            if (!target.isAbstract()) {
+              assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
+              abortMerge = true;
+              return false;
+            }
             // Update the holder of [virtualMethod] using renameMethod().
             DexEncodedMethod resultingVirtualMethod =
                 renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
-            if (appView.options().canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
-                && !target.isAbstract()) {
-              resultingVirtualMethod.accessFlags.unsetAbstract();
-              resultingVirtualMethod =
-                  resultingVirtualMethod.toEmptyThrowingMethod(appView.options());
-            }
             deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
             deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
             add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index c059819..40324ec 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -106,4 +106,12 @@
         clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
   }
 
+  public static <T> boolean contains(T[] elements, T elementToLookFor) {
+    for (Object element : elements) {
+      if (element.equals(elementToLookFor)) {
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanLatticeElement.java b/src/main/java/com/android/tools/r8/utils/BooleanLatticeElement.java
new file mode 100644
index 0000000..9e955c6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BooleanLatticeElement.java
@@ -0,0 +1,80 @@
+// 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.utils;
+
+public abstract class BooleanLatticeElement {
+
+  public static final BooleanLatticeElement BOTTOM =
+      new BooleanLatticeElement() {
+
+        @Override
+        public OptionalBool asOptionalBool() {
+          throw new IllegalStateException("BooleanLatticeElement.BOTTOM is not an OptionalBool");
+        }
+
+        @Override
+        public boolean isBottom() {
+          return true;
+        }
+
+        @Override
+        public String toString() {
+          return "bottom";
+        }
+      };
+
+  BooleanLatticeElement() {}
+
+  public abstract OptionalBool asOptionalBool();
+
+  public boolean isBottom() {
+    return false;
+  }
+
+  public boolean isTrue() {
+    return false;
+  }
+
+  public boolean isFalse() {
+    return false;
+  }
+
+  public boolean isUnknown() {
+    return false;
+  }
+
+  public boolean isPossiblyTrue() {
+    return isTrue() || isUnknown();
+  }
+
+  public boolean isPossiblyFalse() {
+    return isFalse() || isUnknown();
+  }
+
+  public BooleanLatticeElement join(BooleanLatticeElement other) {
+    if (this == other || other.isBottom() || isUnknown()) {
+      return this;
+    }
+    if (isBottom() || other.isUnknown()) {
+      return other;
+    }
+    assert isTrue() || isFalse();
+    assert other.isTrue() || other.isFalse();
+    return OptionalBool.UNKNOWN;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    return this == other;
+  }
+
+  @Override
+  public int hashCode() {
+    return System.identityHashCode(this);
+  }
+
+  // Force all subtypes to implement toString().
+  @Override
+  public abstract String toString();
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index adbaad4..09fa0fe 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -283,6 +283,11 @@
     return className.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR);
   }
 
+  public static String getBinaryNameFromDescriptor(String classDescriptor) {
+    assert isClassDescriptor(classDescriptor);
+    return classDescriptor.substring(1, classDescriptor.length() - 1);
+  }
+
   /**
    * Convert a class binary name to a descriptor.
    *
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 595ac28..3a0b969 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -444,6 +444,8 @@
   public boolean debug = false;
 
   private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
+  private final KotlinOptimizationOptions kotlinOptimizationOptions =
+      new KotlinOptimizationOptions();
   public final TestingOptions testing = new TestingOptions();
 
   public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -463,6 +465,10 @@
     return protoShrinking;
   }
 
+  public KotlinOptimizationOptions kotlinOptimizationOptions() {
+    return kotlinOptimizationOptions;
+  }
+
   public static boolean shouldEnableKeepRuleSynthesisForRecompilation() {
     return System.getProperty("com.android.tools.r8.keepRuleSynthesisForRecompilation") != null;
   }
@@ -925,6 +931,11 @@
     public int threshold = 20;
   }
 
+  public static class KotlinOptimizationOptions {
+    public boolean disableKotlinSpecificOptimizations =
+        System.getProperty("com.android.tools.r8.disableKotlinSpecificOptimizations") != null;
+  }
+
   public static class ProtoShrinkingOptions {
 
     public boolean enableGeneratedExtensionRegistryShrinking =
@@ -933,11 +944,16 @@
     public boolean enableGeneratedMessageLiteShrinking =
         System.getProperty("com.android.tools.r8.generatedMessageLiteShrinking") != null;
 
+    public boolean enableGeneratedMessageLiteBuilderShrinking =
+        System.getProperty("com.android.tools.r8.generatedMessageLiteBuilderShrinking") != null;
+
     public boolean traverseOneOfAndRepeatedProtoFields =
         System.getProperty("com.android.tools.r8.traverseOneOfAndRepeatedProtoFields") == null;
 
     public boolean isProtoShrinkingEnabled() {
-      return enableGeneratedExtensionRegistryShrinking || enableGeneratedMessageLiteShrinking;
+      return enableGeneratedExtensionRegistryShrinking
+          || enableGeneratedMessageLiteShrinking
+          || enableGeneratedMessageLiteBuilderShrinking;
     }
   }
 
@@ -970,6 +986,7 @@
 
     public boolean allowTypeErrors =
         !Version.isDev() || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
+    public boolean allowInvokeErrors = false;
     public boolean disableL8AnnotationRemoval = false;
     public boolean allowUnusedProguardConfigurationRules = true;
     public boolean reportUnusedProguardConfigurationRules = false;
@@ -998,7 +1015,9 @@
     public PrintStream whyAreYouNotInliningConsumer = System.out;
     public boolean trackDesugaredAPIConversions =
         System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
-    public boolean keepInheritedInterfaceMethods = false;
+
+    // TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
+    public boolean allowNonAbstractClassesWithAbstractMethods = true;
 
     // Flag to turn on/off JDK11+ nest-access control even when not required (Cf backend)
     public boolean enableForceNestBasedAccessDesugaringForTest = false;
diff --git a/src/main/java/com/android/tools/r8/utils/OptionalBool.java b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
new file mode 100644
index 0000000..54029d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
@@ -0,0 +1,65 @@
+// 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.utils;
+
+public abstract class OptionalBool extends BooleanLatticeElement {
+
+  public static final OptionalBool TRUE =
+      new OptionalBool() {
+
+        @Override
+        public boolean isTrue() {
+          return true;
+        }
+
+        @Override
+        public String toString() {
+          return "true";
+        }
+      };
+
+  public static final OptionalBool FALSE =
+      new OptionalBool() {
+
+        @Override
+        public boolean isFalse() {
+          return true;
+        }
+
+        @Override
+        public String toString() {
+          return "false";
+        }
+      };
+
+  public static final OptionalBool UNKNOWN =
+      new OptionalBool() {
+
+        @Override
+        public boolean isUnknown() {
+          return true;
+        }
+
+        @Override
+        public String toString() {
+          return "unknown";
+        }
+      };
+
+  OptionalBool() {}
+
+  public static OptionalBool of(boolean bool) {
+    return bool ? TRUE : FALSE;
+  }
+
+  public static OptionalBool unknown() {
+    return UNKNOWN;
+  }
+
+  @Override
+  public OptionalBool asOptionalBool() {
+    return this;
+  }
+}
diff --git a/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
index 1ad06e0..a0f6c8d 100644
--- a/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
+++ b/src/test/desugaredLibrary/conversions/DoubleSummaryStatisticsConversions.java
@@ -5,7 +5,6 @@
 package java.util;
 
 import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 
 public class DoubleSummaryStatisticsConversions {
 
@@ -40,6 +39,8 @@
     JD_DOUBLE_MAX_FIELD.setAccessible(true);
   }
 
+  private DoubleSummaryStatisticsConversions() {}
+
   private static Field getField(Class<?> clazz, String name) {
     try {
       return clazz.getDeclaredField(name);
diff --git a/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
index 8351704..8e9b616 100644
--- a/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
+++ b/src/test/desugaredLibrary/conversions/IntSummaryStatisticsConversions.java
@@ -40,6 +40,8 @@
     JD_INT_MAX_FIELD.setAccessible(true);
   }
 
+  private IntSummaryStatisticsConversions() {}
+
   private static Field getField(Class<?> clazz, String name) {
     try {
       return clazz.getDeclaredField(name);
diff --git a/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
index 7096a2a..ae21eb2 100644
--- a/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
+++ b/src/test/desugaredLibrary/conversions/LongSummaryStatisticsConversions.java
@@ -40,6 +40,8 @@
     JD_LONG_MAX_FIELD.setAccessible(true);
   }
 
+  private LongSummaryStatisticsConversions() {}
+
   private static Field getField(Class<?> clazz, String name) {
     try {
       return clazz.getDeclaredField(name);
diff --git a/src/test/desugaredLibrary/conversions/OptionalConversions.java b/src/test/desugaredLibrary/conversions/OptionalConversions.java
index 390b123..908f9bf 100644
--- a/src/test/desugaredLibrary/conversions/OptionalConversions.java
+++ b/src/test/desugaredLibrary/conversions/OptionalConversions.java
@@ -6,7 +6,9 @@
 
 public class OptionalConversions {
 
-  public static j$.util.Optional convert(java.util.Optional optional) {
+  private OptionalConversions() {}
+
+  public static <T> j$.util.Optional<T> convert(java.util.Optional<T> optional) {
     if (optional == null) {
       return null;
     }
@@ -16,7 +18,7 @@
     return j$.util.Optional.empty();
   }
 
-  public static java.util.Optional convert(j$.util.Optional optional) {
+  public static <T> java.util.Optional<T> convert(j$.util.Optional<T> optional) {
     if (optional == null) {
       return null;
     }
diff --git a/src/test/desugaredLibrary/conversions/TimeConversions.java b/src/test/desugaredLibrary/conversions/TimeConversions.java
index b5adf3c..1320546 100644
--- a/src/test/desugaredLibrary/conversions/TimeConversions.java
+++ b/src/test/desugaredLibrary/conversions/TimeConversions.java
@@ -6,6 +6,8 @@
 
 public class TimeConversions {
 
+  private TimeConversions() {}
+
   public static j$.time.ZonedDateTime convert(java.time.ZonedDateTime dateTime) {
     if (dateTime == null) {
       return null;
diff --git a/src/test/examples/shaking18/Options.java b/src/test/examples/shaking18/Options.java
index 7012340..edb9d77 100644
--- a/src/test/examples/shaking18/Options.java
+++ b/src/test/examples/shaking18/Options.java
@@ -4,7 +4,9 @@
 package shaking18;
 
 public class Options {
-  public boolean alwaysFalse = false;
+  // TODO(b/138913138): member value propagation can behave same with and without initialization.
+  // public boolean alwaysFalse = false;
+  public boolean alwaysFalse;
   public boolean dummy = false;
 
   public Options() {}
diff --git a/src/test/examplesProto/proto2/BuilderTestClass.java b/src/test/examplesProto/proto2/BuilderTestClass.java
deleted file mode 100644
index 9712c22..0000000
--- a/src/test/examplesProto/proto2/BuilderTestClass.java
+++ /dev/null
@@ -1,62 +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 proto2;
-
-import com.android.tools.r8.proto2.TestProto.NestedMessage;
-import com.android.tools.r8.proto2.TestProto.OuterMessage;
-import com.android.tools.r8.proto2.TestProto.Primitives;
-
-public class BuilderTestClass {
-
-  public static void main(String[] args) {
-    builderWithPrimitiveSetters();
-    builderWithReusedSetters();
-    builderWithProtoBuilderSetter();
-    builderWithProtoSetter();
-    builderWithOneofSetter();
-  }
-
-  public static void builderWithPrimitiveSetters() {
-    System.out.println("builderWithPrimitiveSetters");
-    Primitives primitives = Primitives.newBuilder().setFooInt32(17).build();
-    Primitives other = Primitives.newBuilder().setBarInt64(16).build();
-    System.out.println(primitives.getFooInt32());
-    System.out.println(other.getBarInt64());
-  }
-
-  public static void builderWithReusedSetters() {
-    System.out.println("builderWithReusedSetters");
-    Primitives.Builder builder = Primitives.newBuilder().setFooInt32(1);
-    Primitives primitives = builder.build();
-    // Reusing the builder after a build should force copyOnWrite to be kept.
-    Primitives other = builder.setQuxString("qux").build();
-    System.out.println(primitives.getFooInt32());
-    System.out.println(other.getQuxString());
-  }
-
-  public static void builderWithProtoBuilderSetter() {
-    System.out.println("builderWithProtoBuilderSetter");
-    OuterMessage outerMessage =
-        OuterMessage.newBuilder()
-            .setNestedField(NestedMessage.newBuilder().setFooInt64(42))
-            .build();
-    System.out.println(outerMessage.getNestedField().getFooInt64());
-  }
-
-  public static void builderWithProtoSetter() {
-    System.out.println("builderWithProtoSetter");
-    OuterMessage outerMessage =
-        OuterMessage.newBuilder()
-            .setNestedField(NestedMessage.newBuilder().setFooInt64(42).build())
-            .build();
-    System.out.println(outerMessage.getNestedField().getFooInt64());
-  }
-
-  public static void builderWithOneofSetter() {
-    System.out.println("builderWithOneofSetter");
-    Primitives primitives = Primitives.newBuilder().setOneofString("foo").build();
-    System.out.println(primitives.getOneofString());
-  }
-}
diff --git a/src/test/examplesProto/proto2/BuilderWithOneofSetterTestClass.java b/src/test/examplesProto/proto2/BuilderWithOneofSetterTestClass.java
new file mode 100644
index 0000000..cdda6aa
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithOneofSetterTestClass.java
@@ -0,0 +1,16 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class BuilderWithOneofSetterTestClass {
+
+  public static void main(String[] args) {
+    System.out.println("builderWithOneofSetter");
+    Primitives primitives = Primitives.newBuilder().setOneofString("foo").build();
+    Printer.print(primitives);
+  }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithPrimitiveSettersTestClass.java b/src/test/examplesProto/proto2/BuilderWithPrimitiveSettersTestClass.java
new file mode 100644
index 0000000..27a0abd
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithPrimitiveSettersTestClass.java
@@ -0,0 +1,18 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class BuilderWithPrimitiveSettersTestClass {
+
+  public static void main(String[] args) {
+    System.out.println("builderWithPrimitiveSetters");
+    Primitives primitives = Primitives.newBuilder().setFooInt32(17).build();
+    Primitives other = Primitives.newBuilder().setBarInt64(16).build();
+    Printer.print(primitives);
+    Printer.print(other);
+  }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithProtoBuilderSetterTestClass.java b/src/test/examplesProto/proto2/BuilderWithProtoBuilderSetterTestClass.java
new file mode 100644
index 0000000..6dde164
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithProtoBuilderSetterTestClass.java
@@ -0,0 +1,20 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.NestedMessage;
+import com.android.tools.r8.proto2.TestProto.OuterMessage;
+
+public class BuilderWithProtoBuilderSetterTestClass {
+
+  public static void main(String[] args) {
+    System.out.println("builderWithProtoBuilderSetter");
+    OuterMessage outerMessage =
+        OuterMessage.newBuilder()
+            .setNestedField(NestedMessage.newBuilder().setFooInt64(42))
+            .build();
+    System.out.println(outerMessage.getNestedField().getFooInt64());
+  }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithProtoSetterTestClass.java b/src/test/examplesProto/proto2/BuilderWithProtoSetterTestClass.java
new file mode 100644
index 0000000..1200684
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithProtoSetterTestClass.java
@@ -0,0 +1,20 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.NestedMessage;
+import com.android.tools.r8.proto2.TestProto.OuterMessage;
+
+public class BuilderWithProtoSetterTestClass {
+
+  public static void main(String[] args) {
+    System.out.println("builderWithProtoSetter");
+    OuterMessage outerMessage =
+        OuterMessage.newBuilder()
+            .setNestedField(NestedMessage.newBuilder().setFooInt64(42).build())
+            .build();
+    System.out.println(outerMessage.getNestedField().getFooInt64());
+  }
+}
diff --git a/src/test/examplesProto/proto2/BuilderWithReusedSettersTestClass.java b/src/test/examplesProto/proto2/BuilderWithReusedSettersTestClass.java
new file mode 100644
index 0000000..badcdbc
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderWithReusedSettersTestClass.java
@@ -0,0 +1,20 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class BuilderWithReusedSettersTestClass {
+
+  public static void main(String[] args) {
+    System.out.println("builderWithReusedSetters");
+    Primitives.Builder builder = Primitives.newBuilder().setFooInt32(1);
+    Primitives primitives = builder.build();
+    // Reusing the builder after a build should force copyOnWrite to be kept.
+    Primitives other = builder.setQuxString("qux").build();
+    Printer.print(primitives);
+    Printer.print(other);
+  }
+}
diff --git a/src/test/examplesProto/proto2/Printer.java b/src/test/examplesProto/proto2/Printer.java
new file mode 100644
index 0000000..fd87ef0
--- /dev/null
+++ b/src/test/examplesProto/proto2/Printer.java
@@ -0,0 +1,23 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+
+public class Printer {
+
+  static void print(Primitives primitives) {
+    System.out.println(primitives.hasFooInt32());
+    System.out.println(primitives.getFooInt32());
+    System.out.println(primitives.hasOneofString());
+    System.out.println(primitives.getOneofString());
+    System.out.println(primitives.hasOneofUint32());
+    System.out.println(primitives.getOneofUint32());
+    System.out.println(primitives.hasBarInt64());
+    System.out.println(primitives.getBarInt64());
+    System.out.println(primitives.hasQuxString());
+    System.out.println(primitives.getQuxString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index 1754bff..1defe48 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.List;
@@ -27,21 +28,21 @@
 
   @Parameters(name = "{0}, library: {1}")
   public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withAllRuntimes().build(), CfVm.values());
+    return buildParameters(
+        getTestParameters().withAllRuntimes().build(), TestRuntime.getCheckedInCfRuntimes());
   }
 
   private final TestParameters parameters;
-  private final CfVm library;
+  private final CfRuntime library;
 
-  public CompileWithJdkClassFileProviderTest(TestParameters parameters, CfVm library) {
+  public CompileWithJdkClassFileProviderTest(TestParameters parameters, CfRuntime library) {
     this.parameters = parameters;
     this.library = library;
   }
 
   @Test
   public void compileSimpleCodeWithJdkLibrary() throws Exception {
-    ClassFileResourceProvider provider =
-        JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+    ClassFileResourceProvider provider = JdkClassFileProvider.fromJdkHome(library.getJavaHome());
 
     testForR8(parameters.getBackend())
         .addLibraryProvider(provider)
@@ -58,7 +59,7 @@
   @Test
   public void compileSimpleCodeWithSystemJdk() throws Exception {
     // Don't run duplicate tests (library is not used by the test).
-    assumeTrue(library == CfVm.JDK8);
+    assumeTrue(library.getVm() == CfVm.JDK8);
 
     ClassFileResourceProvider provider = JdkClassFileProvider.fromSystemJdk();
 
@@ -76,8 +77,7 @@
 
   @Test
   public void compileCodeWithJava9APIUsage() throws Exception {
-    ClassFileResourceProvider provider =
-        JdkClassFileProvider.fromJdkHome(TestRuntime.getCheckedInJDKHome(library));
+    ClassFileResourceProvider provider = JdkClassFileProvider.fromJdkHome(library.getJavaHome());
 
     TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder =
         testForR8(parameters.getBackend())
@@ -85,7 +85,7 @@
             .addProgramClassFileData(dumpClassWhichUseJava9Flow())
             .addKeepMainRule("MySubscriber");
 
-    if (library == CfVm.JDK8) {
+    if (library.getVm() == CfVm.JDK8) {
       try {
         // java.util.concurrent.Flow$Subscriber is not present in JDK8 rt.jar.
         testBuilder.compileWithExpectedDiagnostics(
diff --git a/src/test/java/com/android/tools/r8/DumpInputsTest.java b/src/test/java/com/android/tools/r8/DumpInputsTest.java
index b8b3a04..e7f2ef8 100644
--- a/src/test/java/com/android/tools/r8/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/DumpInputsTest.java
@@ -41,8 +41,7 @@
   public void testDumpToFile() throws Exception {
     Path dump = temp.newFolder().toPath().resolve("dump.zip");
     try {
-      testForExternalR8(parameters.getBackend())
-          .useExternalJDK(parameters.getRuntime().asCf().getVm())
+      testForExternalR8(parameters.getBackend(), parameters.getRuntime())
           .addJvmFlag("-Dcom.android.tools.r8.dumpinputtofile=" + dump)
           .addProgramClasses(TestClass.class)
           .compile();
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index ae4a36c..0ebe047 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -5,12 +5,10 @@
 package com.android.tools.r8;
 
 import static com.android.tools.r8.ToolHelper.CLASSPATH_SEPARATOR;
-import static com.android.tools.r8.ToolHelper.getJavaExecutable;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
@@ -53,18 +51,22 @@
   private List<Path> proguardConfigFiles = new ArrayList<>();
 
   // External JDK to use to run R8
-  private CfVm externalJDK = null;
+  private final TestRuntime runtime;
 
   private boolean addR8ExternalDeps = false;
 
   private List<String> jvmFlags = new ArrayList<>();
 
-  private ExternalR8TestBuilder(TestState state, Builder builder, Backend backend) {
+  private ExternalR8TestBuilder(
+      TestState state, Builder builder, Backend backend, TestRuntime runtime) {
     super(state, builder, backend);
+    assert runtime != null;
+    this.runtime = runtime;
   }
 
-  public static ExternalR8TestBuilder create(TestState state, Backend backend) {
-    return new ExternalR8TestBuilder(state, R8Command.builder(), backend);
+  public static ExternalR8TestBuilder create(
+      TestState state, Backend backend, TestRuntime runtime) {
+    return new ExternalR8TestBuilder(state, R8Command.builder(), backend, runtime);
   }
 
   @Override
@@ -72,18 +74,6 @@
     return this;
   }
 
-  public ExternalR8TestBuilder useExternalJDK(CfVm externalJDK) {
-    this.externalJDK = externalJDK;
-    return self();
-  }
-
-  private String getJDKToRun() {
-    if (externalJDK == null) {
-      return getJavaExecutable();
-    }
-    return getJavaExecutable(externalJDK);
-  }
-
   public ExternalR8TestBuilder addJvmFlag(String flag) {
     jvmFlags.add(flag);
     return self();
@@ -106,7 +96,10 @@
               : r8jar.toAbsolutePath().toString();
 
       List<String> command = new ArrayList<>();
-      Collections.addAll(command, getJDKToRun());
+      if (runtime.isDex()) {
+        throw new Unimplemented();
+      }
+      Collections.addAll(command, runtime.asCf().getJavaExecutable().toString());
 
       command.addAll(jvmFlags);
 
diff --git a/src/test/java/com/android/tools/r8/JavaCompilerTool.java b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
index d527670..c7f7652 100644
--- a/src/test/java/com/android/tools/r8/JavaCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
@@ -7,7 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -23,24 +23,24 @@
 
 public class JavaCompilerTool {
 
-  private final CfVm jdk;
+  private final CfRuntime jdk;
   private final TestState state;
   private final List<Path> sources = new ArrayList<>();
   private final List<Path> classpath = new ArrayList<>();
   private final List<String> options = new ArrayList<>();
   private Path output = null;
 
-  private JavaCompilerTool(CfVm jdk, TestState state) {
+  private JavaCompilerTool(CfRuntime jdk, TestState state) {
     this.jdk = jdk;
     this.state = state;
   }
 
-  public static JavaCompilerTool create(CfVm jdk, TemporaryFolder temp) {
+  public static JavaCompilerTool create(CfRuntime jdk, TemporaryFolder temp) {
     assert temp != null;
     return create(jdk, new TestState(temp));
   }
 
-  public static JavaCompilerTool create(CfVm jdk, TestState state) {
+  public static JavaCompilerTool create(CfRuntime jdk, TestState state) {
     assert state != null;
     return new JavaCompilerTool(jdk, state);
   }
@@ -106,7 +106,7 @@
   private ProcessResult compileInternal(Path output) throws IOException {
     Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
     List<String> cmdline = new ArrayList<>();
-    cmdline.add(ToolHelper.getJavaExecutable(jdk) + "c");
+    cmdline.add(jdk.getJavaExecutable() + "c");
     cmdline.addAll(options);
     if (!classpath.isEmpty()) {
       cmdline.add("-cp");
diff --git a/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
index d6f38af..fbcc9d6 100644
--- a/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/JdkClassFileProviderTest.java
@@ -4,18 +4,40 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.Opcodes;
 
+@RunWith(Parameterized.class)
 public class JdkClassFileProviderTest extends TestBase implements Opcodes {
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  final TestParameters parameters;
+
+  public JdkClassFileProviderTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private CfRuntime getRuntime() {
+    return parameters.getRuntime().asCf();
+  }
+
   @Test
-  public void testInvalid8RuntimeClassPath() throws Exception {
+  public void testInvalidRuntimeClassPath() throws Exception {
     Path path = temp.newFolder().toPath();
     try {
       JdkClassFileProvider.fromJdkHome(path);
@@ -27,22 +49,23 @@
   }
 
   @Test
-  public void testJdk8JavHome() throws Exception {
+  public void testJdkJavaHome() throws Exception {
     ClassFileResourceProvider provider =
-        JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+        JdkClassFileProvider.fromJdkHome(getRuntime().getJavaHome());
     assertJavaLangObject(provider);
     assert provider instanceof AutoCloseable;
+    if (getRuntime().isNewerThanOrEqual(CfVm.JDK9)) {
+      assertJavaUtilConcurrentFlowSubscriber(provider);
+    }
     ((AutoCloseable) provider).close();
   }
 
   @Test
   public void testJdk8RuntimeClassPath() throws Exception {
+    assumeTrue(getRuntime().getVm() == CfVm.JDK8);
     ClassFileResourceProvider provider =
         JdkClassFileProvider.fromJavaRuntimeJar(
-            ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8)
-                .resolve("jre")
-                .resolve("lib")
-                .resolve("rt.jar"));
+            getRuntime().getJavaHome().resolve("jre").resolve("lib").resolve("rt.jar"));
     assertJavaLangObject(provider);
     assert provider instanceof AutoCloseable;
     ((AutoCloseable) provider).close();
@@ -50,8 +73,9 @@
 
   @Test
   public void testJdk8SystemModules() throws Exception {
+    assumeTrue(getRuntime().getVm() == CfVm.JDK8);
     try {
-      JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK8));
+      JdkClassFileProvider.fromSystemModulesJdk(getRuntime().getJavaHome());
       fail("Not supposed to succeed");
     } catch (NoSuchFileException e) {
       assertThat(e.toString(), containsString("lib/jrt-fs.jar"));
@@ -59,39 +83,10 @@
   }
 
   @Test
-  public void testJdk9JavaHome() throws Exception {
+  public void testJdk9PlusSystemModules() throws Exception {
+    assumeTrue(getRuntime().isNewerThanOrEqual(CfVm.JDK9));
     ClassFileResourceProvider provider =
-        JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
-    assertJavaLangObject(provider);
-    assertJavaUtilConcurrentFlowSubscriber(provider);
-    assert provider instanceof AutoCloseable;
-    ((AutoCloseable) provider).close();
-  }
-
-  @Test
-  public void testJdk9SystemModules() throws Exception {
-    ClassFileResourceProvider provider =
-        JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK9));
-    assertJavaLangObject(provider);
-    assertJavaUtilConcurrentFlowSubscriber(provider);
-    assert provider instanceof AutoCloseable;
-    ((AutoCloseable) provider).close();
-  }
-
-  @Test
-  public void testJdk11JavaHome() throws Exception {
-    ClassFileResourceProvider provider =
-        JdkClassFileProvider.fromJdkHome(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
-    assertJavaLangObject(provider);
-    assertJavaUtilConcurrentFlowSubscriber(provider);
-    assert provider instanceof AutoCloseable;
-    ((AutoCloseable) provider).close();
-  }
-
-  @Test
-  public void testJdk11SystemModules() throws Exception {
-    ClassFileResourceProvider provider =
-        JdkClassFileProvider.fromSystemModulesJdk(ToolHelper.getJavaHome(TestRuntime.CfVm.JDK11));
+        JdkClassFileProvider.fromSystemModulesJdk(getRuntime().getJavaHome());
     assertJavaLangObject(provider);
     assertJavaUtilConcurrentFlowSubscriber(provider);
     assert provider instanceof AutoCloseable;
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 4f31cc8..e44287f 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -48,7 +48,7 @@
       throws IOException {
     assert runtime.isCf();
     ProcessResult result =
-        ToolHelper.runJava(runtime.asCf().getVm(), classpath, ObjectArrays.concat(mainClass, args));
+        ToolHelper.runJava(runtime.asCf(), classpath, ObjectArrays.concat(mainClass, args));
     return new JvmTestRunResult(builder.build(), runtime, result);
   }
 
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index e66a3de..268709d 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -28,21 +28,27 @@
 
   private final CfRuntime jdk;
   private final TestState state;
+  private final String kotlincJar;
   private final List<Path> sources = new ArrayList<>();
   private final List<Path> classpath = new ArrayList<>();
   private Path output = null;
 
-  private KotlinCompilerTool(CfRuntime jdk, TestState state) {
+  private KotlinCompilerTool(CfRuntime jdk, TestState state, Path kotlincJar) {
     this.jdk = jdk;
     this.state = state;
+    this.kotlincJar = kotlincJar == null ? KT_COMPILER : kotlincJar.toString();
   }
 
   public static KotlinCompilerTool create(CfRuntime jdk, TemporaryFolder temp) {
-    return create(jdk, new TestState(temp));
+    return create(jdk, new TestState(temp), null);
   }
 
-  public static KotlinCompilerTool create(CfRuntime jdk, TestState state) {
-    return new KotlinCompilerTool(jdk, state);
+  public static KotlinCompilerTool create (CfRuntime jdk, TemporaryFolder temp, Path kotlincJar) {
+    return create(jdk, new TestState(temp), kotlincJar);
+  }
+
+  public static KotlinCompilerTool create(CfRuntime jdk, TestState state, Path kotlincJar) {
+    return new KotlinCompilerTool(jdk, state, kotlincJar);
   }
 
   public KotlinCompilerTool addSourceFiles(Path files) {
@@ -94,13 +100,13 @@
   private ProcessResult compileInternal(Path output) throws IOException {
     Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
     List<String> cmdline = new ArrayList<>();
-    cmdline.add(ToolHelper.getJavaExecutable(jdk.getVm()));
+    cmdline.add(jdk.getJavaExecutable().toString());
     cmdline.add("-jar");
     cmdline.add(KT_PRELOADER);
     cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
     cmdline.add("-cp");
-    cmdline.add(KT_COMPILER);
-    cmdline.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
+    cmdline.add(kotlincJar);
+    cmdline.add(ToolHelper.K2JVMCompiler);
     for (Path source : sources) {
       cmdline.add(source.toString());
     }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 7b7f270..74cff92 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -102,7 +102,8 @@
       DexVm.Version.V6_0_1,
       DexVm.Version.V7_0_0,
       DexVm.Version.V8_1_0,
-      DexVm.Version.V9_0_0);
+      DexVm.Version.V9_0_0,
+      DexVm.Version.V10_0_0);
 
   // Input jar for jctf tests.
   private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
@@ -478,6 +479,14 @@
   static {
     ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
     builder
+        .put(DexVm.Version.V10_0_0, ImmutableList.of(
+            // TODO(b/144975341): Triage, Verif error.
+            "518-null-array-get",
+            // TODO(b/144975341): Triage, Linking error.
+            "457-regs",
+            "543-env-long-ref",
+            "454-get-vreg"
+        ))
         .put(DexVm.Version.V9_0_0, ImmutableList.of(
             // TODO(120400625): Triage.
             "454-get-vreg",
@@ -1993,6 +2002,11 @@
       vms.add(TestRuntime.getDefaultJavaRuntime());
     } else {
       for (DexVm vm : TestParametersBuilder.getAvailableDexVms()) {
+        // TODO(144966342): Disabled for triaging failures
+        if (vm.getVersion() == DexVm.Version.V10_0_0) {
+          System.out.println("Running on 10.0.0 is disabled, see b/144966342");
+          continue;
+        }
         vms.add(new DexRuntime(vm));
       }
     }
diff --git a/src/test/java/com/android/tools/r8/SanityCheck.java b/src/test/java/com/android/tools/r8/SanityCheck.java
index ef7d3c3..527fadd 100644
--- a/src/test/java/com/android/tools/r8/SanityCheck.java
+++ b/src/test/java/com/android/tools/r8/SanityCheck.java
@@ -6,25 +6,32 @@
 
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.util.Enumeration;
-import java.util.Set;
+import java.util.function.Predicate;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import org.junit.Test;
 
 public class SanityCheck extends TestBase {
 
-  private void checkJarContent(Path jar, boolean allowDirectories, Set<String> additionalEntries)
+  private static final String SRV_PREFIX = "META-INF/services/";
+  private static final String METADATA_EXTENSION =
+      "com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions";
+  private static final String EXT_IN_SRV = SRV_PREFIX + METADATA_EXTENSION;
+
+    private void checkJarContent(
+      Path jar, boolean allowDirectories, Predicate<String> entryTester)
       throws Exception {
     ZipFile zipFile;
     try {
@@ -47,7 +54,7 @@
         // Allow.
       } else if (name.equals("LICENSE")) {
         licenseSeen = true;
-      } else if (additionalEntries.contains(name)) {
+      } else if (entryTester.test(name)) {
         // Allow.
       } else if (name.endsWith("/")) {
         assertTrue("Unexpected directory entry in" + jar, allowDirectories);
@@ -58,32 +65,45 @@
     assertTrue("No LICENSE entry found in " + jar, licenseSeen);
   }
 
-  private void checkLibJarContent(Path jar) throws Exception {
+  private void checkLibJarContent(Path jar, Path map) throws Exception {
     if (!Files.exists(jar)) {
       return;
     }
-    checkJarContent(jar, false, ImmutableSet.of());
+    assertTrue(Files.exists(map));
+    ClassNameMapper mapping = ClassNameMapper.mapperFromFile(map);
+    checkJarContent(jar, false, name -> metadataExtensionTester(name, mapping));
   }
 
   private void checkJarContent(Path jar) throws Exception {
     if (!Files.exists(jar)) {
       return;
     }
-    checkJarContent(
-        jar,
-        true,
-        ImmutableSet.of(
-            "META-INF/services/"
-                + "com.android.tools.r8."
-                + "jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions"));
+    checkJarContent(jar, true, name -> metadataExtensionTester(name, null));
+  }
+
+  private boolean metadataExtensionTester(String name, ClassNameMapper mapping) {
+    if (name.equals(EXT_IN_SRV)) {
+      assertNull(mapping);
+      return true;
+    }
+    if (mapping != null && name.startsWith(SRV_PREFIX)) {
+      String obfuscatedName = name.substring(SRV_PREFIX.length());
+      String originalName =
+          mapping.getObfuscatedToOriginalMapping().original
+              .getOrDefault(obfuscatedName, obfuscatedName);
+      if (originalName.equals(METADATA_EXTENSION)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   @Test
   public void testLibJarsContent() throws Exception {
-    checkLibJarContent(ToolHelper.R8LIB_JAR);
-    checkLibJarContent(ToolHelper.R8LIB_EXCLUDE_DEPS_JAR);
-    checkLibJarContent(ToolHelper.COMPATDXLIB_JAR);
-    checkLibJarContent(ToolHelper.COMPATPROGUARDLIB_JAR);
+    checkLibJarContent(ToolHelper.R8LIB_JAR, ToolHelper.R8LIB_MAP);
+    checkLibJarContent(ToolHelper.R8LIB_EXCLUDE_DEPS_JAR, ToolHelper.R8LIB_EXCLUDE_DEPS_MAP);
+    checkLibJarContent(ToolHelper.COMPATDXLIB_JAR, ToolHelper.COMPATDXLIB_MAP);
+    checkLibJarContent(ToolHelper.COMPATPROGUARDLIB_JAR, ToolHelper.COMPATPROGUARDLIB_MAP);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1d3ce80..b90b40c 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -98,8 +97,9 @@
     return R8CompatTestBuilder.create(new TestState(temp), backend, forceProguardCompatibility);
   }
 
-  public static ExternalR8TestBuilder testForExternalR8(TemporaryFolder temp, Backend backend) {
-    return ExternalR8TestBuilder.create(new TestState(temp), backend);
+  public static ExternalR8TestBuilder testForExternalR8(
+      TemporaryFolder temp, Backend backend, TestRuntime runtime) {
+    return ExternalR8TestBuilder.create(new TestState(temp), backend, runtime);
   }
 
   public static D8TestBuilder testForD8(TemporaryFolder temp) {
@@ -134,8 +134,8 @@
     return testForR8Compat(temp, backend, forceProguardCompatibility);
   }
 
-  public ExternalR8TestBuilder testForExternalR8(Backend backend) {
-    return testForExternalR8(temp, backend);
+  public ExternalR8TestBuilder testForExternalR8(Backend backend, TestRuntime runtime) {
+    return testForExternalR8(temp, backend, runtime);
   }
 
   public D8TestBuilder testForD8() {
@@ -175,10 +175,10 @@
   }
 
   public JavaCompilerTool javac(CfRuntime jdk) {
-    return JavaCompilerTool.create(jdk.getVm(), temp);
+    return JavaCompilerTool.create(jdk, temp);
   }
 
-  public static JavaCompilerTool javac(CfVm jdk, TemporaryFolder temp) {
+  public static JavaCompilerTool javac(CfRuntime jdk, TemporaryFolder temp) {
     return JavaCompilerTool.create(jdk, temp);
   }
 
@@ -186,6 +186,10 @@
     return KotlinCompilerTool.create(jdk, temp);
   }
 
+  public KotlinCompilerTool kotlinc(CfRuntime jdk, Path kotlincJar) {
+    return KotlinCompilerTool.create(jdk, temp, kotlincJar);
+  }
+
   public static KotlinCompilerTool kotlinc(CfRuntime jdk, TemporaryFolder temp) {
     return KotlinCompilerTool.create(jdk, temp);
   }
@@ -1198,6 +1202,7 @@
     }
   }
 
+  @Deprecated
   public static Path runtimeJar(TestParameters parameters) {
     if (parameters.isDexRuntime()) {
       return ToolHelper.getAndroidJar(parameters.getRuntime().asDex().getMinApiLevel());
@@ -1207,7 +1212,6 @@
     }
   }
 
-  @Deprecated
   public static Path runtimeJar(Backend backend) {
     if (backend == Backend.DEX) {
       return ToolHelper.getDefaultAndroidJar();
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 8c643ff..13059f6 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -237,6 +237,11 @@
     return self();
   }
 
+  public CR assertOnlyErrors() {
+    getDiagnosticMessages().assertOnlyErrors();
+    return self();
+  }
+
   public CR assertInfoMessageThatMatches(Matcher<String> matcher) {
     getDiagnosticMessages().assertInfoMessageThatMatches(matcher);
     return self();
@@ -257,6 +262,16 @@
     return self();
   }
 
+  public CR assertErrorMessageThatMatches(Matcher<String> matcher) {
+    getDiagnosticMessages().assertErrorMessageThatMatches(matcher);
+    return self();
+  }
+
+  public CR assertNoErrorMessageThatMatches(Matcher<String> matcher) {
+    getDiagnosticMessages().assertNoErrorMessageThatMatches(matcher);
+    return self();
+  }
+
   public CR disassemble(PrintStream ps) throws IOException, ExecutionException {
     ToolHelper.disassemble(app, ps);
     return self();
@@ -294,8 +309,7 @@
         .addAll(additionalClassPath)
         .add(out)
         .build();
-    ProcessResult result =
-        ToolHelper.runJava(runtime.asCf().getVm(), vmArguments, classPath, arguments);
+    ProcessResult result = ToolHelper.runJava(runtime.asCf(), vmArguments, classPath, arguments);
     return createRunResult(runtime, result);
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index 70e545e..d72ba77 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -24,6 +24,7 @@
     ART_V7_0_0,
     ART_V8_1_0,
     ART_V9_0_0,
+    ART_V10_0_0,
     ART_DEFAULT,
     JAVA;
 
@@ -46,6 +47,8 @@
           return ART_V8_1_0;
         case V9_0_0:
           return ART_V9_0_0;
+        case V10_0_0:
+          return ART_V10_0_0;
         case DEFAULT:
           return ART_DEFAULT;
         default:
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 5759c3a..702815a 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -36,4 +36,8 @@
   public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
 
   public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
+
+  public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher);
+
+  public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher);
 }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 8282c77..478a60d 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -171,4 +171,14 @@
   public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
     return assertNoMessageThatMatches(getWarnings(), "warning", matcher);
   }
+
+  @Override
+  public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher) {
+    return assertMessageThatMatches(getErrors(), "error", matcher);
+  }
+
+  @Override
+  public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher) {
+    return assertNoMessageThatMatches(getErrors(), "error", matcher);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index f2504f2..35d37cc 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -3,14 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.TestRuntime.NoneRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -219,54 +217,45 @@
 
   // Public method to check that the CF runtime coincides with the system runtime.
   public static boolean isSystemJdk(CfVm vm) {
-    String version = System.getProperty("java.version");
-    switch (vm) {
-      case JDK8:
-        return version.startsWith("1.8.");
-      case JDK9:
-        return version.startsWith("9.");
-      case JDK11:
-        return version.startsWith("11.");
-    }
-    throw new Unreachable();
-  }
-
-  private static boolean isSupportedJdk(CfVm vm) {
-    return isSystemJdk(vm) || TestRuntime.isCheckedInJDK(vm);
+    TestRuntime systemRuntime = TestRuntime.getSystemRuntime();
+    return systemRuntime.isCf() && systemRuntime.asCf().getVm().equals(vm);
   }
 
   public static boolean isRuntimesPropertySet() {
     return getRuntimesProperty() != null;
   }
 
-  private static String getRuntimesProperty() {
+  public static String getRuntimesProperty() {
     return System.getProperty("runtimes");
   }
 
-  private static Stream<TestRuntime> getAvailableRuntimes() {
-    Stream<TestRuntime> runtimes;
-    if (isRuntimesPropertySet()) {
-      runtimes =
-          Arrays.stream(getRuntimesProperty().split(":"))
-              .filter(s -> !s.isEmpty())
-              .map(
-                  name -> {
-                    TestRuntime runtime = TestRuntime.fromName(name);
-                    if (runtime != null) {
-                      return runtime;
-                    }
-                    throw new RuntimeException("Unexpected runtime property name: " + name);
-                  });
-    } else {
-      runtimes =
-          Stream.concat(
-              Stream.of(NoneRuntime.getInstance()),
-              Stream.concat(
-                  Arrays.stream(TestRuntime.CfVm.values()).map(CfRuntime::new),
-                  Arrays.stream(DexVm.Version.values()).map(DexRuntime::new)));
+  private static Stream<TestRuntime> getUnfilteredAvailableRuntimes() {
+    // The runtimes are built in a linked hash map to ensure a deterministic order and avoid
+    // duplicates.
+    LinkedHashMap<TestRuntime, TestRuntime> runtimes = new LinkedHashMap<>();
+    // Place the none-runtime first.
+    NoneRuntime noneRuntime = NoneRuntime.getInstance();
+    runtimes.putIfAbsent(noneRuntime, noneRuntime);
+    // Then the checked in runtimes (CF and DEX).
+    for (TestRuntime checkedInRuntime : TestRuntime.getCheckedInRuntimes()) {
+      runtimes.putIfAbsent(checkedInRuntime, checkedInRuntime);
     }
-    // TODO(b/127785410) Support multiple VMs at the same time.
-    return runtimes.filter(runtime -> !runtime.isCf() || isSupportedJdk(runtime.asCf().getVm()));
+    // Then finally the system runtime. It will likely be the same as a checked in and adding it
+    // makes the overall order more stable.
+    TestRuntime systemRuntime = TestRuntime.getSystemRuntime();
+    runtimes.putIfAbsent(systemRuntime, systemRuntime);
+    return runtimes.values().stream();
+  }
+
+  private static Stream<TestRuntime> getAvailableRuntimes() {
+    if (isRuntimesPropertySet()) {
+      String[] runtimeFilters = getRuntimesProperty().split(":");
+      return getUnfilteredAvailableRuntimes()
+          .filter(
+              runtime ->
+                  Arrays.stream(runtimeFilters).anyMatch(filter -> runtime.name().equals(filter)));
+    }
+    return getUnfilteredAvailableRuntimes();
   }
 
   public static List<CfVm> getAvailableCfVms() {
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index d834ebc..c008660 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -6,31 +6,20 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableMap;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
 
 // Base class for the runtime structure in the test parameters.
-public class TestRuntime {
-
-  public static TestRuntime fromName(String name) {
-    if (NoneRuntime.NAME.equals(name)) {
-      return NoneRuntime.getInstance();
-    }
-    CfVm cfVm = CfVm.fromName(name);
-    if (cfVm != null) {
-      return new CfRuntime(cfVm);
-    }
-    if (name.startsWith("dex-")) {
-      DexVm dexVm = DexVm.fromShortName(name.substring(4) + "_host");
-      if (dexVm != null) {
-        return new DexRuntime(dexVm);
-      }
-    }
-    return null;
-  }
+public abstract class TestRuntime {
 
   // Enum describing the possible/supported CF runtimes.
   public enum CfVm {
@@ -79,51 +68,99 @@
     }
   }
 
-  // Values are the path in third_party/openjdk to the repository with bin
-  public static ImmutableMap<CfVm, Path> CHECKED_IN_JDKS = initializeCheckedInJDKs();
+  private static final Path JDK8_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk8");
+  private static final Path JDK9_PATH =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "openjdk-9.0.4");
+  private static final Path JDK11_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-11");
 
-  public static ImmutableMap<CfVm, Path> initializeCheckedInJDKs() {
+  public static CfRuntime getCheckedInJdk8() {
+    Path home;
     if (ToolHelper.isLinux()) {
-      return ImmutableMap.of(
-          CfVm.JDK8,
-          Paths.get("jdk8", "linux-x86"),
-          CfVm.JDK9,
-          Paths.get("openjdk-9.0.4", "linux"),
-          CfVm.JDK11,
-          Paths.get("jdk-11", "Linux"));
+      home = JDK8_PATH.resolve("linux-x86");
+    } else if (ToolHelper.isMac()) {
+      home = JDK8_PATH.resolve("darwin-x86");
+    } else {
+      assert ToolHelper.isWindows();
+      return null;
     }
-    if (ToolHelper.isMac()) {
-      return ImmutableMap.of(
-          CfVm.JDK8,
-          Paths.get("jdk8", "darwin-x86"),
-          CfVm.JDK9,
-          Paths.get("openjdk-9.0.4", "osx"),
-          CfVm.JDK11,
-          Paths.get("jdk-11", "Mac", "Contents", "Home"));
+    return new CfRuntime(CfVm.JDK8, home);
+  }
+
+  public static CfRuntime getCheckedInJdk9() {
+    Path home;
+    if (ToolHelper.isLinux()) {
+      home = JDK9_PATH.resolve("linux");
+    } else if (ToolHelper.isMac()) {
+      home = JDK9_PATH.resolve("osx");
+    } else {
+      assert ToolHelper.isWindows();
+      home = JDK9_PATH.resolve("windows");
     }
-    assert ToolHelper.isWindows();
-    return ImmutableMap.of(
-        CfVm.JDK9,
-        Paths.get("openjdk-9.0.4", "windows"),
-        CfVm.JDK11,
-        Paths.get("jdk-11", "Windows"));
+    return new CfRuntime(CfVm.JDK9, home);
   }
 
-  public static boolean isCheckedInJDK(CfVm jdk) {
-    return CHECKED_IN_JDKS.containsKey(jdk);
+  public static CfRuntime getCheckedInJdk11() {
+    Path home;
+    if (ToolHelper.isLinux()) {
+      home = JDK11_PATH.resolve("Linux");
+    } else if (ToolHelper.isMac()) {
+      home = Paths.get(JDK11_PATH.toString(), "Mac", "Contents", "Home");
+    } else {
+      assert ToolHelper.isWindows();
+      home = JDK11_PATH.resolve("Windows");
+    }
+    return new CfRuntime(CfVm.JDK11, home);
   }
 
-  public static Path getCheckedInJDKHome(CfVm jdk) {
-    return Paths.get("third_party", "openjdk").resolve(CHECKED_IN_JDKS.get(jdk));
+  public static List<CfRuntime> getCheckedInCfRuntimes() {
+    CfRuntime[] jdks =
+        new CfRuntime[] {getCheckedInJdk8(), getCheckedInJdk9(), getCheckedInJdk11()};
+    Builder<CfRuntime> builder = ImmutableList.builder();
+    for (CfRuntime jdk : jdks) {
+      if (jdk != null) {
+        builder.add(jdk);
+      }
+    }
+    return builder.build();
   }
 
-  public static Path getCheckedInJDKPathFor(CfVm jdk) {
-    return getCheckedInJDKHome(jdk).resolve(Paths.get("bin", "java"));
+  private static List<DexRuntime> getCheckedInDexRuntimes() {
+    if (ToolHelper.isLinux()) {
+      return ListUtils.map(Arrays.asList(DexVm.Version.values()), DexRuntime::new);
+    }
+    assert ToolHelper.isMac() || ToolHelper.isWindows();
+    return ImmutableList.of();
   }
 
+  // For compatibility with old tests not specifying a Java runtime
+  @Deprecated
   public static TestRuntime getDefaultJavaRuntime() {
-    // For compatibility with old tests not specifying a Java runtime
-    return new CfRuntime(CfVm.JDK9);
+    return getCheckedInJdk9();
+  }
+
+  public static List<TestRuntime> getCheckedInRuntimes() {
+    return ImmutableList.<TestRuntime>builder()
+        .addAll(getCheckedInCfRuntimes())
+        .addAll(getCheckedInDexRuntimes())
+        .build();
+  }
+
+  public static TestRuntime getSystemRuntime() {
+    String version = System.getProperty("java.version");
+    String home = System.getProperty("java.home");
+    if (version == null || version.isEmpty() || home == null || home.isEmpty()) {
+      throw new Unimplemented("Unable to create a system runtime");
+    }
+    if (version.startsWith("1.8.")) {
+      return new CfRuntime(CfVm.JDK8, Paths.get(home));
+    }
+    if (version.startsWith("9.")) {
+      return new CfRuntime(CfVm.JDK9, Paths.get(home));
+    }
+    if (version.startsWith("11.")) {
+      return new CfRuntime(CfVm.JDK11, Paths.get(home));
+    }
+    throw new Unimplemented("No support for JDK version: " + version);
   }
 
   public static class NoneRuntime extends TestRuntime {
@@ -138,9 +175,24 @@
     }
 
     @Override
+    public String name() {
+      return NAME;
+    }
+
+    @Override
     public String toString() {
       return NAME;
     }
+
+    @Override
+    public boolean equals(Object other) {
+      return this == other;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
   }
 
   // Wrapper for the DEX runtimes.
@@ -158,6 +210,11 @@
     }
 
     @Override
+    public String name() {
+      return "dex-" + vm.getVersion().toString();
+    }
+
+    @Override
     public boolean isDex() {
       return true;
     }
@@ -172,6 +229,20 @@
     }
 
     @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof DexRuntime)) {
+        return false;
+      }
+      DexRuntime dexRuntime = (DexRuntime) other;
+      return vm == dexRuntime.vm;
+    }
+
+    @Override
+    public int hashCode() {
+      return vm.hashCode();
+    }
+
+    @Override
     public String toString() {
       return "dex-" + vm.getVersion().toString();
     }
@@ -183,12 +254,26 @@
 
   // Wrapper for the CF runtimes.
   public static class CfRuntime extends TestRuntime {
-
     private final CfVm vm;
+    private final Path home;
 
-    public CfRuntime(CfVm vm) {
+    public CfRuntime(CfVm vm, Path home) {
       assert vm != null;
       this.vm = vm;
+      this.home = home.toAbsolutePath();
+    }
+
+    @Override
+    public String name() {
+      return vm.name().toLowerCase();
+    }
+
+    public Path getJavaHome() {
+      return home;
+    }
+
+    public Path getJavaExecutable() {
+      return home.resolve("bin").resolve("java");
     }
 
     @Override
@@ -212,7 +297,24 @@
 
     @Override
     public boolean equals(Object obj) {
-      return obj instanceof CfRuntime && ((CfRuntime) obj).vm.equals(vm);
+      if (!(obj instanceof CfRuntime)) {
+        return false;
+      }
+      CfRuntime cfRuntime = (CfRuntime) obj;
+      return vm == cfRuntime.vm && home.equals(cfRuntime.home);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(vm, home);
+    }
+
+    public boolean isNewerThan(CfVm version) {
+      return !vm.lessThanOrEqual(version);
+    }
+
+    public boolean isNewerThanOrEqual(CfVm version) {
+      return vm == version || !vm.lessThanOrEqual(version);
     }
   }
 
@@ -241,4 +343,12 @@
     }
     throw new Unreachable("Unexpected runtime without backend: " + this);
   }
+
+  @Override
+  public abstract boolean equals(Object other);
+
+  @Override
+  public abstract int hashCode();
+
+  public abstract String name();
 }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index c6b305f..84fc2ee 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -145,6 +145,11 @@
         "-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
   }
 
+  public T addKeepMainRules(List<String> mainClasses) {
+    mainClasses.forEach(this::addKeepMainRule);
+    return self();
+  }
+
   public T addKeepMethodRules(Class<?> clazz, String... methodSignatures) {
     StringBuilder sb = new StringBuilder();
     sb.append("-keep class " + clazz.getTypeName() + " {\n");
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 0445166..772648ce 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -11,7 +11,7 @@
 
 import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
 import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
@@ -129,9 +129,12 @@
       "third_party/rhino-android-1.1.1/rhino-android-1.1.1.jar";
   public static final String RHINO_JAR = "third_party/rhino-1.7.10/rhino-1.7.10.jar";
   static final String KT_PRELOADER = "third_party/kotlin/kotlinc/lib/kotlin-preloader.jar";
-  static final String KT_COMPILER = "third_party/kotlin/kotlinc/lib/kotlin-compiler.jar";
+  public static final String KT_COMPILER = "third_party/kotlin/kotlinc/lib/kotlin-compiler.jar";
+  public static final String K2JVMCompiler = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler";
   public static final String KT_STDLIB = "third_party/kotlin/kotlinc/lib/kotlin-stdlib.jar";
   public static final String KT_REFLECT = "third_party/kotlin/kotlinc/lib/kotlin-reflect.jar";
+  public static final String KT_SCRIPT_RT =
+      "third_party/kotlin/kotlinc/lib/kotlin-script-runtime.jar";
   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
   private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
 
@@ -156,11 +159,16 @@
   public static final Path R8_WITH_RELOCATED_DEPS_JAR_11 =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps_11.jar");
   public static final Path R8LIB_JAR = Paths.get(LIBS_DIR, "r8lib.jar");
+  public static final Path R8LIB_MAP = Paths.get(LIBS_DIR, "r8lib.jar.map");
   public static final Path COMPATDX_JAR = Paths.get(LIBS_DIR, "compatdx.jar");
   public static final Path COMPATDXLIB_JAR = Paths.get(LIBS_DIR, "compatdxlib.jar");
+  public static final Path COMPATDXLIB_MAP = Paths.get(LIBS_DIR, "compatdxlib.jar.map");
   public static final Path COMPATPROGUARD_JAR = Paths.get(LIBS_DIR, "compatproguard.jar");
   public static final Path COMPATPROGUARDLIB_JAR = Paths.get(LIBS_DIR, "compatproguardlib.jar");
+  public static final Path COMPATPROGUARDLIB_MAP = Paths.get(LIBS_DIR, "compatproguardlib.jar.map");
   public static final Path R8LIB_EXCLUDE_DEPS_JAR = Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar");
+  public static final Path R8LIB_EXCLUDE_DEPS_MAP =
+      Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar.map");
   public static final Path DEPS_NOT_RELOCATED = Paths.get(LIBS_DIR, "deps-not-relocated.jar");
 
   public static final Path DESUGAR_LIB_JSON_FOR_TESTING =
@@ -217,6 +225,8 @@
     ART_8_1_0_HOST(Version.V8_1_0, Kind.HOST),
     ART_9_0_0_TARGET(Version.V9_0_0, Kind.TARGET),
     ART_9_0_0_HOST(Version.V9_0_0, Kind.HOST),
+    ART_10_0_0_TARGET(Version.V10_0_0, Kind.TARGET),
+    ART_10_0_0_HOST(Version.V10_0_0, Kind.HOST),
     ART_DEFAULT(Version.DEFAULT, Kind.HOST);
 
     private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
@@ -232,6 +242,7 @@
       V7_0_0("7.0.0"),
       V8_1_0("8.1.0"),
       V9_0_0("9.0.0"),
+      V10_0_0("10.0.0"),
       DEFAULT("default");
 
       Version(String shortName) {
@@ -524,6 +535,7 @@
   private static final Map<DexVm, String> ART_DIRS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "art")
+          .put(DexVm.ART_10_0_0_HOST, "art-10.0.0")
           .put(DexVm.ART_9_0_0_HOST, "art-9.0.0")
           .put(DexVm.ART_8_1_0_HOST, "art-8.1.0")
           .put(DexVm.ART_7_0_0_HOST, "art-7.0.0")
@@ -534,6 +546,7 @@
   private static final Map<DexVm, String> ART_BINARY_VERSIONS =
       ImmutableMap.<DexVm, String>builder()
           .put(DexVm.ART_DEFAULT, "bin/art")
+          .put(DexVm.ART_10_0_0_HOST, "bin/art")
           .put(DexVm.ART_9_0_0_HOST, "bin/art")
           .put(DexVm.ART_8_1_0_HOST, "bin/art")
           .put(DexVm.ART_7_0_0_HOST, "bin/art")
@@ -543,12 +556,13 @@
           .put(DexVm.ART_4_0_4_HOST, "bin/dalvik").build();
 
   private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
-      ImmutableMap.of(
-          DexVm.ART_DEFAULT, "bin/art",
-          DexVm.ART_9_0_0_HOST, "bin/art",
-          DexVm.ART_8_1_0_HOST, "bin/art",
-          DexVm.ART_7_0_0_HOST, "bin/art",
-          DexVm.ART_6_0_1_HOST, "bin/art");
+      ImmutableMap.<DexVm, String>builder()
+          .put(DexVm.ART_DEFAULT, "bin/art")
+          .put(DexVm.ART_10_0_0_HOST, "bin/art")
+          .put(DexVm.ART_9_0_0_HOST, "bin/art")
+          .put(DexVm.ART_8_1_0_HOST, "bin/art")
+          .put(DexVm.ART_7_0_0_HOST, "bin/art")
+          .put(DexVm.ART_6_0_1_HOST, "bin/art").build();
 
   private static final List<String> DALVIK_BOOT_LIBS =
       ImmutableList.of(
@@ -568,6 +582,7 @@
     ImmutableMap.Builder<DexVm, List<String>> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, ART_BOOT_LIBS)
+        .put(DexVm.ART_10_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_9_0_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_8_1_0_HOST, ART_BOOT_LIBS)
         .put(DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS)
@@ -584,6 +599,7 @@
     ImmutableMap.Builder<DexVm, String> builder = ImmutableMap.builder();
     builder
         .put(DexVm.ART_DEFAULT, "angler")
+        .put(DexVm.ART_10_0_0_HOST, "coral")
         .put(DexVm.ART_9_0_0_HOST, "marlin")
         .put(DexVm.ART_8_1_0_HOST, "marlin")
         .put(DexVm.ART_7_0_0_HOST, "angler")
@@ -891,6 +907,8 @@
     switch (dexVm.version) {
       case DEFAULT:
         return AndroidApiLevel.O;
+      case V10_0_0:
+        return AndroidApiLevel.Q;
       case V9_0_0:
         return AndroidApiLevel.P;
       case V8_1_0:
@@ -1258,28 +1276,28 @@
 
   public static ProcessResult runJava(List<String> vmArgs, List<Path> classpath, String... args)
       throws IOException {
-    return runJava(null, vmArgs, classpath, args);
+    return runJava(TestRuntime.getSystemRuntime().asCf(), vmArgs, classpath, args);
   }
 
-  public static ProcessResult runJava(CfVm runtime, List<Path> classpath, String... args)
+  public static ProcessResult runJava(CfRuntime runtime, List<Path> classpath, String... args)
       throws IOException {
     return runJava(runtime, ImmutableList.of(), classpath, args);
   }
 
+  @Deprecated
   public static ProcessResult runKotlinc(
-      CfVm runtime,
       List<Path> classPaths,
       Path directoryToCompileInto,
       List<String> extraOptions,
       Path... filesToCompile)
       throws IOException {
-    List<String> cmdline = new ArrayList<>(Arrays.asList(getJavaExecutable(runtime)));
+    List<String> cmdline = new ArrayList<>(Arrays.asList(getJavaExecutable()));
     cmdline.add("-jar");
     cmdline.add(KT_PRELOADER);
     cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
     cmdline.add("-cp");
     cmdline.add(KT_COMPILER);
-    cmdline.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
+    cmdline.add(K2JVMCompiler);
     String[] strings = Arrays.stream(filesToCompile).map(Path::toString).toArray(String[]::new);
     Collections.addAll(cmdline, strings);
     cmdline.add("-d");
@@ -1302,10 +1320,11 @@
   }
 
   public static ProcessResult runJava(
-      CfVm runtime, List<String> vmArgs, List<Path> classpath, String... args) throws IOException {
+      CfRuntime runtime, List<String> vmArgs, List<Path> classpath, String... args)
+      throws IOException {
     String cp =
         classpath.stream().map(Path::toString).collect(Collectors.joining(CLASSPATH_SEPARATOR));
-    List<String> cmdline = new ArrayList<String>(Arrays.asList(getJavaExecutable(runtime)));
+    List<String> cmdline = new ArrayList<>(Arrays.asList(runtime.getJavaExecutable().toString()));
     cmdline.addAll(vmArgs);
     cmdline.add("-cp");
     cmdline.add(cp);
@@ -1407,27 +1426,13 @@
   }
 
   @Deprecated
-  // Use getJavaExecutable(CfVm) to specify a JDK version or getSystemJavaExecutable
+  // Use CfRuntime.getJavaExecutable() for a specific JDK or getSystemJavaExecutable
   public static String getJavaExecutable() {
     return getSystemJavaExecutable();
   }
 
   public static String getSystemJavaExecutable() {
-    return Paths.get(System.getProperty("java.home"), "bin", "java").toString();
-  }
-
-  public static String getJavaExecutable(CfVm runtime) {
-    if (TestRuntime.isCheckedInJDK(runtime)) {
-      return TestRuntime.getCheckedInJDKPathFor(runtime).toString();
-    } else {
-      // TODO(b/127785410): Always assume a non-null runtime.
-      assert runtime == null || TestParametersBuilder.isSystemJdk(runtime);
-      return getSystemJavaExecutable();
-    }
-  }
-
-  public static Path getJavaHome(CfVm runtime) {
-    return TestRuntime.getCheckedInJDKHome(runtime);
+    return TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString();
   }
 
   public static ProcessResult runArtRaw(ArtCommandBuilder builder) throws IOException {
@@ -1787,6 +1792,7 @@
   public static ProcessResult runDex2OatRaw(Path file, Path outFile, DexVm vm) throws IOException {
     // TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
     Assume.assumeTrue(ToolHelper.isDex2OatSupported());
+    Assume.assumeFalse("b/144975341", vm.version == DexVm.Version.V10_0_0);
     if (vm.isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
       // Run default dex2oat for tests on dalvik runtimes.
       vm = DexVm.ART_DEFAULT;
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
rename to src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index a1e1f56..a19cfec 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.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 com.android.tools.r8.cf;
+package com.android.tools.r8.cf.bootstrap;
 
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.google.common.io.ByteStreams.toByteArray;
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.FileUtils;
@@ -54,6 +55,7 @@
   private static Pair<Path, Path> r8R8Debug;
   private static Pair<Path, Path> r8R8Release;
 
+  private final TestParameters parameters;
   private static boolean testExternal = true;
 
   @ClassRule public static TemporaryFolder testFolder = new TemporaryFolder();
@@ -72,7 +74,7 @@
   }
 
   public BootstrapCurrentEqualityTest(TestParameters parameters) {
-    // TODO: use parameters to run on the right java.
+    this.parameters = parameters;
   }
 
   private static Pair<Path, Path> compileR8(CompilationMode mode) throws Exception {
@@ -80,7 +82,7 @@
     final Path jar = testFolder.newFolder().toPath().resolve("out.jar");
     final Path map = testFolder.newFolder().toPath().resolve("out.map");
     if (testExternal) {
-      testForExternalR8(newTempFolder(), Backend.CF)
+      testForExternalR8(newTempFolder(), Backend.CF, TestRuntime.getCheckedInJdk9())
           .useR8WithRelocatedDeps()
           .setMode(mode)
           .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
@@ -121,7 +123,7 @@
       return;
     }
     Path runR81 =
-        testForExternalR8(Backend.CF)
+        testForExternalR8(parameters.getBackend(), parameters.getRuntime())
             .useProvidedR8(ToolHelper.R8LIB_JAR)
             .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
             .addKeepRuleFiles(MAIN_KEEP)
@@ -129,7 +131,7 @@
             .compile()
             .outputJar();
     Path runR82 =
-        testForExternalR8(Backend.CF)
+        testForExternalR8(parameters.getBackend(), parameters.getRuntime())
             .useProvidedR8(ToolHelper.R8LIB_EXCLUDE_DEPS_JAR)
             .addR8ExternalDepsToClasspath()
             .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
@@ -151,7 +153,7 @@
   private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
       throws Exception {
     ExternalR8TestCompileResult runR8Debug =
-        testForExternalR8(newTempFolder(), Backend.CF)
+        testForExternalR8(newTempFolder(), parameters.getBackend(), parameters.getRuntime())
             .useR8WithRelocatedDeps()
             .addProgramFiles(program)
             .addKeepRules(keep)
@@ -159,7 +161,7 @@
             .compile();
     assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar(), args).toString());
     ExternalR8TestCompileResult runR8Release =
-        testForExternalR8(newTempFolder(), Backend.CF)
+        testForExternalR8(newTempFolder(), parameters.getBackend(), parameters.getRuntime())
             .useR8WithRelocatedDeps()
             .addProgramFiles(program)
             .addKeepRules(keep)
@@ -181,7 +183,7 @@
       CompilationMode mode)
       throws Exception {
     ExternalR8TestCompileResult runR8R8 =
-        testForExternalR8(newTempFolder(), Backend.CF)
+        testForExternalR8(newTempFolder(), parameters.getBackend(), parameters.getRuntime())
             .useProvidedR8(r8.getFirst())
             .addProgramFiles(program)
             .addKeepRules(keep)
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
similarity index 99%
rename from src/test/java/com/android/tools/r8/cf/BootstrapTest.java
rename to src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
index addc3ec..722e4f6 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.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 com.android.tools.r8.cf;
+package com.android.tools.r8.cf.bootstrap;
 
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.google.common.io.ByteStreams.toByteArray;
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/Hello.kt b/src/test/java/com/android/tools/r8/cf/bootstrap/Hello.kt
new file mode 100644
index 0000000..fdfc655
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/Hello.kt
@@ -0,0 +1,8 @@
+// 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.cf.bootstrap
+
+fun main(args : Array<String>) {
+  println("I'm Woody. Howdy, howdy, howdy.")
+}
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
new file mode 100644
index 0000000..c7a6bc0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
@@ -0,0 +1,133 @@
+// 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.cf.bootstrap;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.internal.CompilationTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Ignore;
+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 KotlinCompilerTreeShakingTest extends CompilationTestBase {
+
+  private static final String PKG_NAME = KotlinCompilerTreeShakingTest.class.getPackage().getName();
+  private static final Path HELLO_KT =
+      Paths.get(
+          ToolHelper.TESTS_DIR,
+          "java",
+          DescriptorUtils.getBinaryNameFromJavaType(PKG_NAME),
+          "Hello.kt");
+  private static final int MAX_SIZE = (int) (31361268 * 0.4);
+
+  private TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public KotlinCompilerTreeShakingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testForRuntime() throws Exception {
+    // Compile Hello.kt and make sure it works as expected.
+    Path classPathBefore =
+        kotlinc(parameters.getRuntime().asCf())
+            .addSourceFiles(HELLO_KT)
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
+        .addClasspath(classPathBefore)
+        .run(parameters.getRuntime(), PKG_NAME + ".HelloKt")
+        .assertSuccessWithOutputLines("I'm Woody. Howdy, howdy, howdy.");
+  }
+
+  @Ignore(
+      "b/136457753: assertion error in static class merger; "
+          + "b/144861881: force-inlining of non-inlineable constructors in vertical class merger; "
+          + "b/144877828: assertion error in method naming state during interface method renaming; "
+          + "b/144859533: umbrella"
+  )
+  @Test
+  public void test() throws Exception {
+    List<Path> libs =
+        ImmutableList.of(
+            ToolHelper.getKotlinStdlibJar(),
+            ToolHelper.getKotlinReflectJar(),
+            Paths.get(ToolHelper.KT_SCRIPT_RT));
+    // Process kotlin-compiler.jar.
+    Path r8ProcessedKotlinc =
+        testForR8(parameters.getBackend())
+            .addLibraryFiles(libs)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .addProgramFiles(Paths.get(ToolHelper.KT_COMPILER))
+            .addKeepAttributes("*Annotation*")
+            .addKeepClassAndMembersRules(ToolHelper.K2JVMCompiler)
+            .addKeepClassAndMembersRules("**.K2JVMCompilerArguments")
+            .addKeepClassAndMembersRules("**.*Argument*")
+            .addKeepClassAndMembersRules("**.Freezable")
+            .addKeepRules(
+                "-keepclassmembers class * {",
+                "  *** parseCommandLineArguments(...);",
+                "}"
+            )
+            .addKeepRules(
+                "-keepclassmembers,allowoptimization enum * {",
+                "    public static **[] values();",
+                "    public static ** valueOf(java.lang.String);",
+                "}")
+            .addOptionsModification(o -> {
+              // Ignore com.sun.tools.javac.main.JavaCompiler and others
+              // Resulting jar may not be able to deal with .java source files, though.
+              o.ignoreMissingClasses = true;
+              // b/144861100: invoke-static on interface is allowed up to JDK 8.
+              o.testing.allowInvokeErrors = true;
+              // TODO(b/144861881): force-inlining of non-inlineable constructors.
+              o.enableVerticalClassMerging = false;
+            })
+            .compile()
+            .writeToZip();
+
+    // Copy libraries used by kotlin-compiler.jar so that Preloader can load them.
+    Path dir = r8ProcessedKotlinc.getParent();
+    for (Path lib : libs) {
+      Path newLib = dir.resolve(lib.getFileName());
+      Files.copy(lib, newLib, REPLACE_EXISTING);
+    }
+
+    // TODO(b/144859533): passing `dir` as -kotlin-home.
+    // Compile Hello.kt again with r8-processed kotlin-compiler.jar
+    Path classPathAfter =
+        kotlinc(parameters.getRuntime().asCf(), r8ProcessedKotlinc)
+            .addSourceFiles(HELLO_KT)
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
+        .addClasspath(classPathAfter)
+        .run(parameters.getRuntime(), PKG_NAME + ".HelloKt")
+        .assertSuccessWithOutputLines("I'm Woody. Howdy, howdy, howdy.");
+
+    int size = applicationSize(AndroidApp.builder().addProgramFile(r8ProcessedKotlinc).build());
+    assertTrue(size <= MAX_SIZE);
+  }
+}
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 1a5c127..ae8012a 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
@@ -5,12 +5,11 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.desugar.desugaredlibrary.conversiontests.APIConversionTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -33,7 +32,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class CustomCollectionTest extends CoreLibDesugarTestBase {
+public class CustomCollectionTest extends APIConversionTestBase {
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
@@ -63,11 +62,10 @@
             .compile()
             .inspect(
                 inspector -> {
-                  this.assertNoWrappers(inspector);
                   this.assertCustomCollectionCallsCorrect(inspector, false);
                 })
             .addDesugaredCoreLibraryRunClassPath(
-                this::buildDesugaredLibrary,
+                this::buildDesugaredLibraryWithConversionExtension,
                 parameters.getApiLevel(),
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary)
@@ -82,13 +80,6 @@
       // Expected output is emulated interfaces expected output.
       assertLines2By2Correct(stdOut);
     }
-    String[] split = stdErr.split("Could not find method");
-    if (split.length > 2) {
-      fail("Could not find multiple methods");
-    } else if (split.length == 2) {
-      // On some VMs the Serialized lambda code is missing.
-      assertTrue(stdErr.contains("SerializedLambda"));
-    }
   }
 
   @Test
@@ -108,7 +99,6 @@
             .compile()
             .inspect(
                 inspector -> {
-                  this.assertNoWrappers(inspector);
                   this.assertCustomCollectionCallsCorrect(inspector, true);
                 })
             .addDesugaredCoreLibraryRunClassPath(
@@ -121,11 +111,6 @@
     assertResultCorrect(r8TestRunResult.getStdOut(), r8TestRunResult.getStdErr());
   }
 
-  private void assertNoWrappers(CodeInspector inspector) {
-    assertTrue(inspector.allClasses().stream().noneMatch(cl -> cl.getOriginalName().startsWith(
-        DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
-  }
-
   private void assertCustomCollectionCallsCorrect(CodeInspector inspector, boolean r8) {
     MethodSubject direct = inspector.clazz(EXECUTOR).uniqueMethodWithName("directTypes");
     // TODO(b/134732760): Due to memberRebinding, R8 is not as precise as D8 regarding
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index b60de38..df86791 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
@@ -86,6 +87,9 @@
                     clazz.getOriginalName().startsWith("j$.")
                         || clazz
                             .getOriginalName()
+                            .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)
+                        || clazz
+                            .getOriginalName()
                             .contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)));
     assertThat(inspector.clazz("j$.time.Clock"), isPresent());
     // Above N the following classes are removed instead of being desugared.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
index 0d4f8a2..c45b403 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
@@ -22,7 +21,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class APIConversionTest extends CoreLibDesugarTestBase {
+public class APIConversionTest extends APIConversionTestBase {
 
   private final TestParameters parameters;
 
@@ -61,14 +60,14 @@
         .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .compile()
         .assertOnlyInfos() // No warnings.
-        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, parameters.getApiLevel())
         .run(parameters.getRuntime(), Executor.class)
         .assertSuccessWithOutput(
             StringUtils.lines(
                 "[5, 6, 7]",
                 "$r8$wrapper$java$util$stream$IntStream$-V-WRP",
-                "Unsupported conversion for java.util.IntSummaryStatistics. See compilation time"
-                    + " infos for more details."));
+                "IntSummaryStatistics"));
   }
 
   static class Executor {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
index 7d816e5..29b316d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/APIConversionTestBase.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -26,7 +26,7 @@
             + " windows",
         !ToolHelper.isWindows());
 
-    CfRuntime runtime = new CfRuntime(CfVm.JDK8);
+    CfRuntime runtime = TestRuntime.getCheckedInJdk8();
     Path conversionFolder = temp.newFolder("conversions").toPath();
 
     // Compile the stubs to be able to compile the conversions.
@@ -51,6 +51,10 @@
   }
 
   protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel) {
+    return buildDesugaredLibraryWithConversionExtension(apiLevel, "", false);
+  }
+
+  protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel,String keepRules, boolean shrink) {
     Path[] timeConversionClasses;
     try {
       timeConversionClasses = getConversionClasses();
@@ -59,6 +63,6 @@
     }
     ArrayList<Path> paths = new ArrayList<>();
     Collections.addAll(paths, timeConversionClasses);
-    return buildDesugaredLibrary(apiLevel, "", false, paths);
+    return buildDesugaredLibrary(apiLevel, keepRules, shrink, paths);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
new file mode 100644
index 0000000..79cad2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIDesugaredLibTest.java
@@ -0,0 +1,83 @@
+// 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.conversiontests;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class DuplicateAPIDesugaredLibTest extends APIConversionTestBase {
+
+  @Test
+  public void testLib() throws Exception {
+    Box<Path> desugaredLibBox = new Box<>();
+    Path customLib =
+        testForD8()
+            .addProgramClasses(CustomLibClass.class)
+            .setMinApi(AndroidApiLevel.B)
+            .compile()
+            .writeToZip();
+    String stdOut =
+        testForD8()
+            .setMinApi(AndroidApiLevel.B)
+            .addProgramClasses(Executor.class)
+            .addLibraryClasses(CustomLibClass.class)
+            .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+            .compile()
+            .addDesugaredCoreLibraryRunClassPath(
+                (AndroidApiLevel api) -> {
+                  desugaredLibBox.set(this.buildDesugaredLibraryWithConversionExtension(api));
+                  return desugaredLibBox.get();
+                },
+                AndroidApiLevel.B)
+            .addRunClasspathFiles(customLib)
+            .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+            .assertSuccess()
+            .getStdOut();
+    assertDupMethod(new CodeInspector(desugaredLibBox.get()));
+    assertLines2By2Correct(stdOut);
+  }
+
+  private void assertDupMethod(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz("j$.util.concurrent.ConcurrentHashMap");
+    assertEquals(
+        2,
+        clazz.virtualMethods().stream().filter(m -> m.getOriginalName().equals("forEach")).count());
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      Map<Integer, Double> map = new ConcurrentHashMap<>();
+      map.put(1, 1.1);
+      map.put(2, 2.2);
+      BiConsumer<Integer, Double> biConsumer = (x, y) -> System.out.print(" " + x + " " + y);
+      map.forEach(biConsumer);
+      System.out.println();
+      CustomLibClass.javaForEach(map, biConsumer);
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+
+    @SuppressWarnings("WeakerAccess")
+    public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
+      map.forEach(consumer);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
new file mode 100644
index 0000000..31ca3b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
@@ -0,0 +1,80 @@
+// 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.conversiontests;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+
+public class DuplicateAPIProgramTest extends APIConversionTestBase {
+
+  @Test
+  public void testMap() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    String stdOut =
+        testForD8()
+            .setMinApi(AndroidApiLevel.B)
+            .addProgramClasses(Executor.class, MyMap.class)
+            .addLibraryClasses(CustomLibClass.class)
+            .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+            .compile()
+            .inspect(this::assertDupMethod)
+            .addDesugaredCoreLibraryRunClassPath(
+                this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+            .addRunClasspathFiles(customLib)
+            .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+            .assertSuccess()
+            .getStdOut();
+    assertLines2By2Correct(stdOut);
+  }
+
+  private void assertDupMethod(CodeInspector i) {
+    assertEquals(
+        2,
+        i.clazz(MyMap.class).virtualMethods().stream()
+            .filter(m -> m.getOriginalName().equals("forEach"))
+            .count());
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      IdentityHashMap<Integer, Double> map = new MyMap<>();
+      map.put(1, 1.1);
+      map.put(2, 2.2);
+      BiConsumer<Integer, Double> biConsumer = (x, y) -> System.out.print(" " + x + " " + y);
+      map.forEach(biConsumer);
+      System.out.println();
+      CustomLibClass.javaForEach(map, biConsumer);
+    }
+  }
+
+  public static class MyMap<K, V> extends IdentityHashMap<K, V> {
+
+    @Override
+    public void forEach(BiConsumer<? super K, ? super V> action) {
+      System.out.print("ForEach");
+      super.forEach(action);
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+    @SuppressWarnings("WeakerAccess")
+    public static <K, V> void javaForEach(Map<K, V> map, BiConsumer<K, V> consumer) {
+      map.forEach(consumer);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
index 6e791b6..a281c3d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugar_jdk_libs.json
@@ -60,6 +60,12 @@
         "java.util.SortedSet": "j$.util.SortedSet",
         "java.util.Set": "j$.util.Set",
         "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+      },
+      "custom_conversion": {
+        "java.util.Optional": "j$.util.OptionalConversions",
+        "java.util.OptionalDouble": "j$.util.OptionalConversions",
+        "java.util.OptionalInt": "j$.util.OptionalConversions",
+        "java.util.OptionalLong": "j$.util.OptionalConversions"
       }
     }
   ],
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
index db0a09a..d2501e9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11AtomicTests.java
@@ -10,7 +10,7 @@
 import static org.hamcrest.CoreMatchers.endsWith;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -65,7 +65,7 @@
   public static void compileAtomicClasses() throws Exception {
     File atomicClassesDir = new File(ATOMIC_COMPILED_TESTS_FOLDER.toString());
     assert atomicClassesDir.exists() || atomicClassesDir.mkdirs();
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
         .addSourceFiles(ATOMIC_TESTS_FILES)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
index 71458f0..ff779f5 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ConcurrentMapTests.java
@@ -7,27 +7,23 @@
 import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
-import static junit.framework.TestCase.assertTrue;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.endsWith;
-import static org.hamcrest.CoreMatchers.not;
 
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
 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.CodeInspector;
 import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,7 +73,7 @@
   public static void compileConcurrentClasses() throws Exception {
     File concurrentClassesDir = new File(CONCURRENT_COMPILED_TESTS_FOLDER.toString());
     assert concurrentClassesDir.exists() || concurrentClassesDir.mkdirs();
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
         .addSourceFiles(getAllFilesWithSuffixInDirectory(CONCURRENT_TESTS_FOLDER, JAVA_EXTENSION))
@@ -95,7 +91,7 @@
     Path[] classesToCompile = concurrentHashFilesAndDependencies.toArray(new Path[0]);
     File concurrentHashClassesDir = new File(CONCURRENT_HASH_COMPILED_TESTS_FOLDER.toString());
     assert concurrentHashClassesDir.exists() || concurrentHashClassesDir.mkdirs();
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
         .addSourceFiles(classesToCompile)
@@ -110,6 +106,8 @@
   public void testD8Concurrent() throws Exception {
     // TODO(b/134732760): Support Java 9+ libraries.
     // We skip the ConcurrentRemoveIf test because of the  non desugared class CompletableFuture.
+    Assume.assumeFalse("b/144975341",
+        parameters.getRuntime().asDex().getVm().getVersion() == Version.V10_0_0);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     testForD8()
@@ -120,7 +118,6 @@
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
         .compile()
-        .inspect(this::assertNoConversions)
         .withArt6Plus64BitsLib()
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
@@ -132,15 +129,6 @@
             endsWith(StringUtils.lines("ConcurrentModification: SUCCESS")));
   }
 
-  private void assertNoConversions(CodeInspector inspector) {
-    assertTrue(
-        inspector.allClasses().stream()
-            .noneMatch(
-                cl ->
-                    cl.getOriginalName()
-                        .startsWith(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
-  }
-
   private Path[] concurrentHashTestToCompile() {
     // We exclude WhiteBox.class because of Method handles, they are not supported on old devices
     // and the test uses methods not present even on 28.
@@ -175,6 +163,8 @@
 
   @Test
   public void testD8ConcurrentHash() throws Exception {
+    Assume.assumeFalse("b/144975341",
+        parameters.getRuntime().asDex().getVm().getVersion() == Version.V10_0_0);
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     String verbosity = "2";
     D8TestCompileResult d8TestCompileResult =
@@ -191,7 +181,6 @@
                 parameters.getApiLevel(),
                 keepRuleConsumer.get(),
                 shrinkDesugaredLibrary);
-    System.out.println(keepRuleConsumer.get());
     for (String className : concurrentHashTestNGTestsToRun()) {
       d8TestCompileResult
           .run(parameters.getRuntime(), "TestNGMainRunner", verbosity, className)
@@ -202,9 +191,7 @@
       // Failure implies a runtime exception.
       // We ensure that everything could be resolved (no missing method or class)
       // with the assertion on stderr.
-      d8TestCompileResult
-          .run(parameters.getRuntime(), className).assertSuccess()
-          .assertStderrMatches(not(containsString("Could not")));
+      d8TestCompileResult.run(parameters.getRuntime(), className).assertSuccess();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
index 9a0b63b..b3e6156 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11CoreLibTestBase.java
@@ -8,7 +8,7 @@
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
 
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -80,7 +80,7 @@
             "java.base=ALL-UNNAMED",
             "--patch-module",
             "java.base=" + JDK_11_JAVA_BASE_EXTENSION_FILES_DIR);
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addOptions(options)
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
index 9f64237..69c3558 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11MathTests.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import java.io.File;
@@ -68,14 +69,14 @@
   public static void compileMathClasses() throws Exception {
     File mathClassesDir = new File(JDK_11_MATH_TESTS_DIR.toString());
     assert mathClassesDir.exists() || mathClassesDir.mkdirs();
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addSourceFiles(JDK_11_MATH_JAVA_FILES)
         .setOutputPath(JDK_11_MATH_TESTS_DIR)
         .compile();
 
     File strictMathClassesDir = new File(JDK_11_STRICT_MATH_TESTS_DIR.toString());
     assert strictMathClassesDir.exists() || strictMathClassesDir.mkdirs();
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addSourceFiles(JDK_11_STRICT_MATH_JAVA_FILES)
         .setOutputPath(JDK_11_STRICT_MATH_TESTS_DIR)
         .compile();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
index 730ee1b..c8a3e2d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11ObjectsTests.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
@@ -55,7 +56,7 @@
   public static void compileObjectsClass() throws Exception {
     File objectsDir = new File(JDK_11_OBJECTS_TESTS_DIR.toString());
     assert objectsDir.exists() || objectsDir.mkdirs();
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addSourceFiles(JDK_11_OBJECTS_JAVA_DIR.resolve(BASIC_OBJECTS_TEST + JAVA_EXTENSION))
         .setOutputPath(JDK_11_OBJECTS_TESTS_DIR)
         .compile();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
index 784d320..62e1bf9 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/desugaredlibraryjdktests/Jdk11StreamTests.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -172,7 +172,7 @@
             "java.base=ALL-UNNAMED",
             "--patch-module",
             "java.base=" + JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR);
-    javac(CfVm.JDK11, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
         .addOptions(options)
         .addClasspathFiles(
             Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index 86a4edb..819e6ce 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -43,7 +43,7 @@
   public static void compilePathBackport() throws Exception {
     assumeTrue("JDK8 is not checked-in on Windows", !ToolHelper.isWindows());
     pathMock = getStaticTemp().newFolder("PathMock").toPath();
-    javac(CfVm.JDK8, getStaticTemp())
+    javac(TestRuntime.getCheckedInJdk8(), getStaticTemp())
         .setOutputPath(pathMock)
         .addSourceFiles(
             getAllFilesWithSuffixInDirectory(Paths.get("src/test/r8OnArtBackport"), "java"))
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index 882c545..2533619 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -10,11 +10,11 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
 import com.android.tools.r8.desugar.desugaredlibrary.CoreLibDesugarTestBase;
 import java.io.File;
 import java.nio.file.Path;
@@ -72,7 +72,7 @@
     Path ouputThroughCf = ouputFolder.toPath().resolve("outThroughCf.zip").toAbsolutePath();
     ProcessResult javaProcessResult =
         ToolHelper.runJava(
-            CfVm.JDK9,
+            TestRuntime.getCheckedInJdk9(),
             Collections.singletonList(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR),
             "-Xmx512m",
             R8.class.getTypeName(),
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 073eda1..1f6832c 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
@@ -11,9 +11,10 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.cf.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -51,7 +52,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withCfRuntime(CfVm.JDK11).build();
+    return getTestParameters().withCfRuntimes().build();
   }
 
   @BeforeClass
@@ -95,19 +96,21 @@
     Path prevGeneratedJar = null;
     String prevRunResult = null;
     for (Path jar : jarsToCompare()) {
+      // All jars except ToolHelper.R8_WITH_RELOCATED_DEPS_JAR are compiled for JDK11.
+      TestRuntime runtime =
+          jar == ToolHelper.R8_WITH_RELOCATED_DEPS_JAR
+              ? parameters.getRuntime()
+              : TestRuntime.getCheckedInJdk11();
       Path generatedJar =
-          testForExternalR8(Backend.CF)
+          testForExternalR8(Backend.CF, runtime)
               .useProvidedR8(jar)
-              .useExternalJDK(jar == ToolHelper.R8_WITH_RELOCATED_DEPS_JAR ? null : CfVm.JDK11)
               .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION))
               .addKeepRules(HELLO_KEEP)
               .compile()
               .outputJar();
       String runResult =
           ToolHelper.runJava(
-                  parameters.getRuntime().asCf().getVm(),
-                  ImmutableList.of(generatedJar),
-                  "hello.Hello")
+                  parameters.getRuntime().asCf(), ImmutableList.of(generatedJar), "hello.Hello")
               .toString();
       if (prevRunResult != null) {
         assertEquals(prevRunResult, runResult);
@@ -123,12 +126,13 @@
   @Test
   public void testR8() throws Exception {
     Assume.assumeTrue(!ToolHelper.isWindows());
+    Assume.assumeTrue(parameters.isCfRuntime());
+    Assume.assumeTrue(CfVm.JDK11 == parameters.getRuntime().asCf().getVm());
     Path prevGeneratedJar = null;
     for (Path jar : jarsToCompare()) {
       Path generatedJar =
-          testForExternalR8(Backend.CF)
+          testForExternalR8(Backend.CF, parameters.getRuntime())
               .useProvidedR8(jar)
-              .useExternalJDK(CfVm.JDK11)
               .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION))
               .addKeepRuleFiles(MAIN_KEEP)
               .compile()
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
new file mode 100644
index 0000000..f72a98f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
@@ -0,0 +1,114 @@
+// 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.graph;
+
+import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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.ToolHelper.DexVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeFinalTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeFinalTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCallingFinal()
+      throws IOException, CompilationFailedException, ExecutionException {
+    boolean hasIncorrectSuperLookup =
+        parameters.isDexRuntime()
+            && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+            && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST);
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClasses(Main.class, A.class)
+            .addProgramClassFileData(
+                getClassWithTransformedInvoked(B.class), getClassWithTransformedInvoked(C.class))
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines(
+                "Hello from B",
+                "Hello from B",
+                hasIncorrectSuperLookup ? "Hello from A" : "Hello from B",
+                "Hello from B",
+                "Hello from A",
+                "Hello from B");
+  }
+
+  private byte[] getClassWithTransformedInvoked(Class<?> clazz) throws IOException {
+    return transformer(clazz)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              // The super call to bar() is already INVOKESPECIAL.
+              assertTrue(name.equals("foo") || opcode == INVOKESPECIAL);
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("Hello from A");
+    }
+
+    public void bar() {
+      // TODO(b/110175213): We cannot change this to an invoke-special since this requires a
+      //  direct bridge in DEX.
+      foo();
+    }
+  }
+
+  public static class B extends A {
+
+    // Having a final method allows us to rewrite invoke-special foo() to invoke-virtual foo().
+    public final void foo() {
+      System.out.println("Hello from B");
+    }
+
+    public void bar() {
+      foo();
+      ((A) this).foo();
+      super.bar();
+    }
+  }
+
+  public static class C extends B {
+
+    public void bar() {
+      foo();
+      ((B) this).foo();
+      ((A) this).foo();
+      super.bar();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
new file mode 100644
index 0000000..816288d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
@@ -0,0 +1,79 @@
+// 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.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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 java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialForInvokeVirtualTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialForInvokeVirtualTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(A.class, Main.class)
+            .addProgramClassFileData(getClassBWithTransformedInvoked())
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  private byte[] getClassBWithTransformedInvoked() throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKEVIRTUAL, opcode);
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class B extends A {
+
+    void bar() {
+      foo();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
new file mode 100644
index 0000000..a54fa1d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
@@ -0,0 +1,81 @@
+// 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.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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 java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialForNonDeclaredInvokeVirtualTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialForNonDeclaredInvokeVirtualTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(A.class, B.class, Main.class)
+            .addProgramClassFileData(getClassCWithTransformedInvoked())
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  private byte[] getClassCWithTransformedInvoked() throws IOException {
+    return transformer(C.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKEVIRTUAL, opcode);
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class B extends A {}
+
+  public static class C extends B {
+
+    void bar() {
+      foo();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new C().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
new file mode 100644
index 0000000..f49c764
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
@@ -0,0 +1,90 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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.ToolHelper.DexVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    boolean hasSegmentationFaultOnInvokeSuper =
+        parameters.isDexRuntime()
+            && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+            && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST);
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(I.class, Main.class)
+            .addProgramClassFileData(getClassWithTransformedInvoked())
+            .run(parameters.getRuntime(), Main.class);
+    // TODO(b/110175213): Remove when fixed.
+    if (parameters.isCfRuntime()) {
+      runResult.assertSuccessWithOutputLines("Hello World!");
+    } else {
+      runResult.assertFailureWithErrorThatMatches(
+          containsString(hasSegmentationFaultOnInvokeSuper ? "SIGSEGV" : "NoSuchMethodError"));
+    }
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKEVIRTUAL, opcode);
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class B implements I {
+
+    public void bar() {
+      foo(); // Will be rewritten to invoke-special B.foo()
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
new file mode 100644
index 0000000..1253df8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
@@ -0,0 +1,82 @@
+// 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.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialInterfaceWithBridgeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialInterfaceWithBridgeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(I.class, A.class, Main.class)
+            .addProgramClassFileData(getClassWithTransformedInvoked())
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKEVIRTUAL, opcode);
+              assertEquals(owner, DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()));
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class A implements I {}
+
+  public static class B extends A {
+
+    public void bar() {
+      foo(); // Will be rewritten to invoke-special A.foo()
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
new file mode 100644
index 0000000..e046730
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
@@ -0,0 +1,82 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import com.android.tools.r8.CompilationFailedException;
+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.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/144450911.
+@RunWith(Parameterized.class)
+public class InvokeSpecialMissingInvokeVirtualTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialMissingInvokeVirtualTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    TestRunResult<?> runResult =
+        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+            .addProgramClasses(A.class, Main.class)
+            .addProgramClassFileData(getClassWithTransformedInvoked())
+            .run(parameters.getRuntime(), Main.class)
+            .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(B.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKEVIRTUAL, opcode);
+              assertEquals("notify", name);
+              continuation.apply(
+                  INVOKESPECIAL,
+                  DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+                  "foo",
+                  descriptor,
+                  isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {}
+
+  public static class B extends A {
+
+    public void bar() {
+      notify(); // Will be rewritten to invoke-special A.foo() which is missing.
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
new file mode 100644
index 0000000..00f4bb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
@@ -0,0 +1,81 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeSpecialOnSameClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeSpecialOnSameClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    try {
+      testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+          .addProgramClasses(Main.class)
+          .addProgramClassFileData(getClassWithTransformedInvoked())
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutputLines("Hello World!");
+      // TODO(b/110175213): Remove when fixed.
+      assertTrue(parameters.isCfRuntime());
+    } catch (CompilationFailedException compilation) {
+      assertThat(
+          compilation.getCause().getMessage(),
+          containsString("Failed to compile unsupported use of invokespecial"));
+    }
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(A.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.apply(INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+
+    public void bar() {
+      foo(); // Will be rewritten to invoke-special A.foo()
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
index 5e2d674..83d0b83 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.invokespecial.Main;
@@ -52,13 +53,15 @@
         .assertSuccessWithOutput(StringUtils.lines("true", "false"));
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testD8Behavior() throws Exception {
     // TODO(b/110175213): Should succeed with output "true\nfalse\n".
     testForD8()
         .addProgramFiles(inputJar)
-        .run(Main.class)
-        .assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
+        .compileWithExpectedDiagnostics(
+            testDiagnosticMessages ->
+                testDiagnosticMessages.assertErrorMessageThatMatches(
+                    containsString("Failed to compile unsupported use of invokespecial")));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeVirtualFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeVirtualFinalTest.java
new file mode 100644
index 0000000..781731f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeVirtualFinalTest.java
@@ -0,0 +1,93 @@
+// 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.graph;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.transformers.ClassTransformer;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualFinalTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeVirtualFinalTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testInvokeSpecialOnClassWithFinal()
+      throws ExecutionException, CompilationFailedException, IOException {
+    String expectedError = "overrides final";
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST)) {
+      expectedError = "LinkageError";
+    }
+    testForRuntime(parameters)
+        .addProgramClasses(B.class, Main.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString(expectedError));
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(A.class)
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public MethodVisitor visitMethod(
+                  int access,
+                  String name,
+                  String descriptor,
+                  String signature,
+                  String[] exceptions) {
+                if (name.equals("foo")) {
+                  access |= ACC_FINAL;
+                }
+                return super.visitMethod(access, name, descriptor, signature, exceptions);
+              }
+            })
+        .transform();
+  }
+
+  public static class A {
+    public void foo() {
+      System.out.println("Hello from A");
+    }
+  }
+
+  public static class B extends A {
+    public void foo() {
+      System.out.println("Hello from B");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B().foo();
+      ((A) new B()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
new file mode 100644
index 0000000..67191c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokestatic/InvokeStaticOnInterfaceTest.java
@@ -0,0 +1,122 @@
+// 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.graph.invokestatic;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.TestRuntime.CfVm;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InvokeStaticOnInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public InvokeStaticOnInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws IOException, CompilationFailedException, ExecutionException {
+    assertTrue(parameters.isCfRuntime());
+    TestRunResult<?> runResult =
+        testForRuntime(parameters)
+            .addProgramClasses(I.class)
+            .addProgramClassFileData(getClassWithTransformedInvoked())
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.getRuntime().asCf().isNewerThan(CfVm.JDK8)) {
+      runResult.assertFailureWithErrorThatMatches(
+          containsString(
+              "java.lang.IncompatibleClassChangeError: Method"
+                  + " com.android.tools.r8.graph.invokestatic.InvokeStaticOnInterfaceTest$I.foo()V"
+                  + " must be InterfaceMethodref constant"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World!");
+    }
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testCfInvokeOnStaticInterfaceMethod_failed()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getClassWithTransformedInvoked())
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  @Test
+  public void testCfInvokeOnStaticInterfaceMethod_errorAllowed()
+      throws ExecutionException, CompilationFailedException, IOException {
+    TestRunResult<?> runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class)
+            .addProgramClassFileData(getClassWithTransformedInvoked())
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .addOptionsModification(o -> o.testing.allowInvokeErrors = true)
+            .addKeepMainRule(Main.class)
+            .run(parameters.getRuntime(), Main.class);
+    if (parameters.getRuntime().asCf().isNewerThan(CfVm.JDK8)) {
+      runResult.assertFailureWithErrorThatMatches(
+          containsString(
+              "java.lang.IncompatibleClassChangeError: Method"
+                  + " com.android.tools.r8.graph.invokestatic.a.a()V"
+                  + " must be InterfaceMethodref constant"));
+    } else {
+      runResult.assertSuccessWithOutputLines("Hello World!");
+    }
+  }
+
+  private byte[] getClassWithTransformedInvoked() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              assertEquals(INVOKESTATIC, opcode);
+              assertTrue(isInterface);
+              continuation.apply(opcode, owner, name, descriptor, false);
+            })
+        .transform();
+  }
+
+  @NeverMerge
+  public interface I {
+
+    @NeverInline
+    static void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      I.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 07ccabd..3127150 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -5,15 +5,15 @@
 package com.android.tools.r8.internal.proto;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
@@ -21,93 +21,178 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+// TODO(b/112437944): Strengthen test to ensure that builder inlining succeeds even without single-
+//  and double-caller inlining.
 @RunWith(Parameterized.class)
 public class Proto2BuilderShrinkingTest extends ProtoShrinkingTestBase {
 
   private static final String LITE_BUILDER = "com.google.protobuf.GeneratedMessageLite$Builder";
-  private static final String TEST_CLASS = "proto2.BuilderTestClass";
 
   private static List<Path> PROGRAM_FILES =
       ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
 
-  private final boolean enableMinification;
+  private final List<String> mains;
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{1}, enable minification: {0}")
+  @Parameterized.Parameters(name = "{1}, {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        ImmutableList.of(
+            ImmutableList.of("proto2.BuilderWithOneofSetterTestClass"),
+            ImmutableList.of("proto2.BuilderWithPrimitiveSettersTestClass"),
+            ImmutableList.of("proto2.BuilderWithProtoBuilderSetterTestClass"),
+            ImmutableList.of("proto2.BuilderWithProtoSetterTestClass"),
+            ImmutableList.of("proto2.BuilderWithReusedSettersTestClass"),
+            ImmutableList.of(
+                "proto2.BuilderWithOneofSetterTestClass",
+                "proto2.BuilderWithPrimitiveSettersTestClass",
+                "proto2.BuilderWithProtoBuilderSetterTestClass",
+                "proto2.BuilderWithProtoSetterTestClass",
+                "proto2.BuilderWithReusedSettersTestClass")),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public Proto2BuilderShrinkingTest(boolean enableMinification, TestParameters parameters) {
-    this.enableMinification = enableMinification;
+  public Proto2BuilderShrinkingTest(List<String> mains, TestParameters parameters) {
+    this.mains = mains;
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramFiles(PROGRAM_FILES)
-        .addKeepMainRule(TEST_CLASS)
-        .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
-        .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
-        .addKeepRules("-neverinline class " + TEST_CLASS + " { <methods>; }")
-        .addOptionsModification(
-            options -> {
-              options.enableFieldBitAccessAnalysis = true;
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
-              options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
-              options.enableStringSwitchConversion = true;
-            })
-        .allowAccessModification()
-        .allowUnusedProguardConfigurationRules()
-        .enableInliningAnnotations()
-        .minification(enableMinification)
-        .setMinApi(parameters.getRuntime())
-        .compile()
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), TEST_CLASS)
-        .assertSuccessWithOutputLines(
-            "builderWithPrimitiveSetters",
-            "17",
-            "16",
-            "builderWithReusedSetters",
-            "1",
-            "qux",
-            "builderWithProtoBuilderSetter",
-            "42",
-            "builderWithProtoSetter",
-            "42",
+    R8TestCompileResult result =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(PROGRAM_FILES)
+            .addKeepMainRules(mains)
+            .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+            .addOptionsModification(
+                options -> {
+                  options.applyInliningToInlinee = true;
+                  options.enableFieldBitAccessAnalysis = true;
+                  options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+                  options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
+                  options.enableStringSwitchConversion = true;
+                })
+            .allowAccessModification()
+            .allowUnusedProguardConfigurationRules()
+            .enableInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::inspect);
+
+    // TODO(b/112437944): Should never allow dynamicMethod() to be inlined unless MethodToInvoke is
+    //  guaranteed to be different from MethodToInvoke.BUILD_MESSAGE_INFO.
+    assumeTrue(mains.size() > 1);
+
+    for (String main : mains) {
+      result.run(parameters.getRuntime(), main).assertSuccessWithOutput(getExpectedOutput(main));
+    }
+  }
+
+  private static String getExpectedOutput(String main) {
+    switch (main) {
+      case "proto2.BuilderWithOneofSetterTestClass":
+        return StringUtils.lines(
             "builderWithOneofSetter",
-            "foo");
+            "false",
+            "0",
+            "true",
+            "foo",
+            "false",
+            "0",
+            "false",
+            "0",
+            "false",
+            "");
+      case "proto2.BuilderWithPrimitiveSettersTestClass":
+        return StringUtils.lines(
+            "builderWithPrimitiveSetters",
+            "true",
+            "17",
+            "false",
+            "",
+            "false",
+            "0",
+            "false",
+            "0",
+            "false",
+            "",
+            "false",
+            "0",
+            "false",
+            "",
+            "false",
+            "0",
+            "true",
+            "16",
+            "false",
+            "");
+      case "proto2.BuilderWithProtoBuilderSetterTestClass":
+        return StringUtils.lines("builderWithProtoBuilderSetter", "42");
+      case "proto2.BuilderWithProtoSetterTestClass":
+        return StringUtils.lines("builderWithProtoSetter", "42");
+      case "proto2.BuilderWithReusedSettersTestClass":
+        return StringUtils.lines(
+            "builderWithReusedSetters",
+            "true",
+            "1",
+            "false",
+            "",
+            "false",
+            "0",
+            "false",
+            "0",
+            "false",
+            "",
+            "true",
+            "1",
+            "false",
+            "",
+            "false",
+            "0",
+            "false",
+            "0",
+            "true",
+            "qux");
+      default:
+        throw new Unreachable();
+    }
   }
 
   private void inspect(CodeInspector outputInspector) {
-    ClassSubject liteClassSubject = outputInspector.clazz(LITE_BUILDER);
-    assertThat(liteClassSubject, isPresent());
+    // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
+    //  is kept.
+    assertThat(outputInspector.clazz(LITE_BUILDER), isPresent());
 
-    MethodSubject copyOnWriteMethodSubject = liteClassSubject.uniqueMethodWithName("copyOnWrite");
-    assertThat(copyOnWriteMethodSubject, isPresent());
+    // TODO(b/112437944): Should be absent.
+    assertThat(
+        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
+        isNestedMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
 
-    ClassSubject testClassSubject = outputInspector.clazz(TEST_CLASS);
-    assertThat(testClassSubject, isPresent());
+    // TODO(b/112437944): Should be absent.
+    assertThat(
+        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
+        isOuterMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
 
-    List<String> testNames =
-        ImmutableList.of(
-            "builderWithPrimitiveSetters",
-            "builderWithReusedSetters",
-            "builderWithProtoBuilderSetter",
-            "builderWithProtoSetter",
-            "builderWithOneofSetter");
-    for (String testName : testNames) {
-      MethodSubject methodSubject = testClassSubject.uniqueMethodWithName(testName);
-      assertThat(methodSubject, isPresent());
-      assertTrue(
-          methodSubject
-              .streamInstructions()
-              .filter(InstructionSubject::isInvoke)
-              .map(InstructionSubject::getMethod)
-              // TODO(b/112437944): Only builderWithReusedSetters() should invoke copyOnWrite().
-              .anyMatch(method -> method == copyOnWriteMethodSubject.getMethod().method));
-    }
+    // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
+    //  is kept.
+    assertThat(
+        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
+        isPrimitivesBuilderUsed(mains) ? isPresent() : not(isPresent()));
+  }
+
+  private static boolean isNestedMessageBuilderUsed(List<String> mains) {
+    return mains.contains("proto2.BuilderWithProtoBuilderSetterTestClass")
+        || mains.contains("proto2.BuilderWithProtoSetterTestClass");
+  }
+
+  private static boolean isOuterMessageBuilderUsed(List<String> mains) {
+    return isNestedMessageBuilderUsed(mains);
+  }
+
+  private static boolean isPrimitivesBuilderUsed(List<String> mains) {
+    return mains.contains("proto2.BuilderWithOneofSetterTestClass")
+        || mains.contains("proto2.BuilderWithPrimitiveSettersTestClass")
+        || mains.contains("proto2.BuilderWithReusedSettersTestClass");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 2071b82..aed2c26 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -83,8 +83,9 @@
             .addOptionsModification(
                 options -> {
                   options.enableFieldBitAccessAnalysis = true;
-                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
                   options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+                  options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
                   options.enableStringSwitchConversion = true;
                 })
             .allowAccessModification(allowAccessModification)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 7a67427..faa91e5 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -57,8 +57,10 @@
         .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
         .addOptionsModification(
             options -> {
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+              options.enableFieldBitAccessAnalysis = true;
               options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+              options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
               options.enableStringSwitchConversion = true;
             })
         .allowAccessModification(allowAccessModification)
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index e210196..25c4a53 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -40,7 +40,6 @@
 @RunWith(Parameterized.class)
 public class GenerateBackportMethods extends TestBase {
 
-  static final Path javaExecutable = Paths.get(ToolHelper.getJavaExecutable(CfVm.JDK9));
   static final Path googleFormatDir = Paths.get(ToolHelper.THIRD_PARTY_DIR, "google-java-format");
   static final Path googleFormatJar =
       googleFormatDir.resolve("google-java-format-1.7-all-deps.jar");
@@ -90,7 +89,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
   }
 
   public GenerateBackportMethods(TestParameters parameters) {
@@ -104,15 +103,18 @@
     assertEquals("Classes should be listed in sorted order", sorted, methodTemplateClasses);
     assertEquals(
         FileUtils.readTextFile(backportMethodsFile, StandardCharsets.UTF_8),
-        generateBackportMethods());
+        generateBackportMethods(parameters.getRuntime().asCf().getJavaExecutable().toString()));
   }
 
   // Running this method will regenerate / overwrite the content of the backport methods.
   public static void main(String[] args) throws Exception {
-    FileUtils.writeToFile(backportMethodsFile, null, generateBackportMethods().getBytes());
+    FileUtils.writeToFile(
+        backportMethodsFile,
+        null,
+        generateBackportMethods(ToolHelper.getSystemJavaExecutable()).getBytes());
   }
 
-  private static String generateBackportMethods() throws IOException {
+  private static String generateBackportMethods(String javaExecutable) throws IOException {
     InternalOptions options = new InternalOptions();
     CfCodePrinter codePrinter = new CfCodePrinter();
     JarClassFileReader reader =
@@ -146,7 +148,7 @@
     ProcessBuilder builder =
         new ProcessBuilder(
             ImmutableList.of(
-                javaExecutable.toString(),
+                javaExecutable,
                 "-jar",
                 googleFormatJar.toString(),
                 outfile.toAbsolutePath().toString()));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithControlFlowTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithControlFlowTest.java
new file mode 100644
index 0000000..44db913
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithControlFlowTest.java
@@ -0,0 +1,139 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerBuilderWithControlFlowTest extends ClassInlinerTestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "flow = >0>0>-1>1234>1236>7>3>1240>1266>6>1248>3798>8>1254>10>1264>8885>12>1273>19063>14>"
+              + "16>1288>39449>18>1301>80229>20>1315>161806>23>1335>324994>25>1353>651383>27>1372>1"
+              + "304183>29>1392>2609806>32>1418>5221092>34>1442>10443683>36>1467>20888893>38>1493>4"
+              + "1779342>40>1520>42>1551>83560313>44>1581>167122280>46>1612>334246248>48>1644>66849"
+              + "4219>50>1677>1336990197>52>54>1716>-1620985089>56>1753>1052998963>58>1791>21059998"
+              + "12>60>1830>-82965744>62>1870>-165929517>64>1911>-331857019>");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerBuilderWithControlFlowTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerBuilderWithControlFlowTest.class)
+        .addKeepMainRule(TestClass.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testExpectedBehavior() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(ClassInlinerBuilderWithControlFlowTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+
+    assertEquals(
+        Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+    assertThat(inspector.clazz(ControlFlow.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      ControlFlow flow = new ControlFlow(-1, 2, 7);
+      for (int k = 0; k < 25; k++) {
+        if (k % 3 == 0) {
+          flow.foo(k);
+        } else if (k % 3 == 1) {
+          flow.bar(1, 2, 3, 4);
+        }
+      }
+      System.out.println("flow = " + flow.toString());
+    }
+  }
+
+  static class ControlFlow {
+
+    int a;
+    int b;
+    int c = 1234;
+    int d;
+    String s = ">";
+
+    ControlFlow(int b, int c, int d) {
+      this.s += this.a++ + ">";
+      this.s += this.b + ">";
+      this.b = b;
+      this.s += this.b + ">";
+      this.s += this.c + ">";
+      this.c += c;
+      this.s += this.c + ">";
+      this.s += (this.d = d) + ">";
+    }
+
+    void foo(int count) {
+      for (int i = 0; i < count; i++) {
+        switch (i % 4) {
+          case 0:
+            this.s += ++this.a + ">";
+            break;
+          case 1:
+            this.c += this.b;
+            this.s += this.c + ">";
+            break;
+          case 2:
+            this.d += this.d++ + this.c++ + this.b++ + this.a++;
+            this.s += this.d + ">";
+            break;
+        }
+      }
+    }
+
+    void bar(int a, int b, int c, int d) {
+      this.a += a;
+      this.b += b;
+      this.c += c;
+      this.d += d;
+    }
+
+    @Override
+    public String toString() {
+      return s;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithMoreControlFlowTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithMoreControlFlowTest.java
new file mode 100644
index 0000000..3778db4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerBuilderWithMoreControlFlowTest.java
@@ -0,0 +1,98 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerBuilderWithMoreControlFlowTest extends ClassInlinerTestBase {
+
+  private static final String EXPECTED = StringUtils.lines("Pos(x=0, y=10)");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerBuilderWithMoreControlFlowTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerBuilderWithMoreControlFlowTest.class)
+        .addKeepMainRule(TestClass.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testExpectedBehavior() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(ClassInlinerBuilderWithMoreControlFlowTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+
+    assertEquals(
+        Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+    assertThat(inspector.clazz(Pos.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      String str = "1234567890";
+      Pos pos = new Pos();
+      while (pos.y < str.length()) {
+        pos.x = pos.y;
+        pos.y = pos.x;
+
+        if (str.charAt(pos.x) != '*') {
+          if ('0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
+            while (pos.y < str.length() && '0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
+              pos.y++;
+            }
+          }
+        }
+      }
+      System.out.println(pos.myToString());
+    }
+  }
+
+  static class Pos {
+
+    int x = 0;
+    int y = 0;
+
+    String myToString() {
+      return "Pos(x=" + x + ", y=" + y + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
new file mode 100644
index 0000000..f751e25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderTest.java
@@ -0,0 +1,164 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerSimplePairBuilderTest extends ClassInlinerTestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "[before] first = null",
+          "[after] first = f1",
+          "Pair(f1, <null>)",
+          "[before] second = null",
+          "[after] second = s2",
+          "Pair(<null>, s2)",
+          "[before] first = null",
+          "[after] first = f3",
+          "[before] second = null",
+          "[after] second = s4",
+          "Pair(f3, s4)");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerSimplePairBuilderTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerSimplePairBuilderTest.class)
+        .addKeepMainRule(TestClass.class)
+        // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
+        .addOptionsModification(options -> options.inliningInstructionLimit = 6)
+        .enableInliningAnnotations()
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testExpectedBehavior() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(ClassInlinerSimplePairBuilderTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+    if (parameters.isCfRuntime()) {
+      assertThat(inspector.clazz(PairBuilder.class), isPresent());
+
+      // const-string canonicalization is disabled in CF, which helps ClassInliner identify
+      // PairBuilder as candidate.
+      Set<String> expected =
+          ImmutableSet.of(StringBuilder.class.getTypeName(), PairBuilder.class.getTypeName());
+      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
+      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
+      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
+    } else {
+      assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
+
+      Set<String> expected = ImmutableSet.of(StringBuilder.class.getTypeName());
+      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder1")));
+      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder2")));
+      assertEquals(expected, collectTypes(clazz.uniqueMethodWithName("testSimpleBuilder3")));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testSimpleBuilder1();
+      testSimpleBuilder2();
+      testSimpleBuilder3();
+    }
+
+    @NeverInline
+    static void testSimpleBuilder1() {
+      System.out.println(new PairBuilder<String, String>().setFirst("f1").build().myToString());
+    }
+
+    @NeverInline
+    static void testSimpleBuilder2() {
+      System.out.println(new PairBuilder<String, String>().setSecond("s2").build().myToString());
+    }
+
+    @NeverInline
+    static void testSimpleBuilder3() {
+      System.out.println(
+          new PairBuilder<String, String>().setFirst("f3").setSecond("s4").build().myToString());
+    }
+  }
+
+  static class Pair<F, S> {
+    final F first;
+    final S second;
+
+    Pair(F first, S second) {
+      this.first = first;
+      this.second = second;
+    }
+
+    String myToString() {
+      return "Pair("
+          + (first == null ? "<null>" : first)
+          + ", "
+          + (second == null ? "<null>" : second)
+          + ")";
+    }
+  }
+
+  static class PairBuilder<F, S> {
+
+    F first;
+    S second = null;
+
+    PairBuilder<F, S> setFirst(F first) {
+      System.out.println("[before] first = " + this.first);
+      this.first = first;
+      System.out.println("[after] first = " + this.first);
+      return this;
+    }
+
+    PairBuilder<F, S> setSecond(S second) {
+      System.out.println("[before] second = " + this.second);
+      this.second = second;
+      System.out.println("[after] second = " + this.second);
+      return this;
+    }
+
+    public Pair<F, S> build() {
+      return new Pair<>(first, second);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java
new file mode 100644
index 0000000..6e5dc39
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerSimplePairBuilderWithMultipleBuildsTest.java
@@ -0,0 +1,137 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerSimplePairBuilderWithMultipleBuildsTest extends ClassInlinerTestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "Pair(<null>, <null>)",
+          "[before] first = null",
+          "[after] first = f1",
+          "Pair(f1, <null>)",
+          "[before] second = null",
+          "[after] second = s2",
+          "Pair(f1, s2)");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerSimplePairBuilderWithMultipleBuildsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerSimplePairBuilderWithMultipleBuildsTest.class)
+        .addKeepMainRule(TestClass.class)
+        // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
+        .addOptionsModification(options -> options.inliningInstructionLimit = 6)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testExpectedBehavior() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(ClassInlinerSimplePairBuilderWithMultipleBuildsTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+
+    // Note that Pair created instances were also inlined in the following method since
+    // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
+    // as in the above method, the instance of pair would be passed to println() which
+    // would make it not eligible for inlining.
+    assertEquals(
+        Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+    assertThat(inspector.clazz(PairBuilder.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      PairBuilder<String, String> builder = new PairBuilder<>();
+      Pair p1 = builder.build();
+      System.out.println(p1.myToString());
+      builder.setFirst("f1");
+      Pair p2 = builder.build();
+      System.out.println(p2.myToString());
+      builder.setSecond("s2");
+      Pair p3 = builder.build();
+      System.out.println(p3.myToString());
+    }
+  }
+
+  static class Pair<F, S> {
+
+    final F first;
+    final S second;
+
+    Pair(F first, S second) {
+      this.first = first;
+      this.second = second;
+    }
+
+    String myToString() {
+      return "Pair("
+          + (first == null ? "<null>" : first)
+          + ", "
+          + (second == null ? "<null>" : second)
+          + ")";
+    }
+  }
+
+  static class PairBuilder<F, S> {
+
+    F first;
+    S second = null;
+
+    void setFirst(F first) {
+      System.out.println("[before] first = " + this.first);
+      this.first = first;
+      System.out.println("[after] first = " + this.first);
+    }
+
+    void setSecond(S second) {
+      System.out.println("[before] second = " + this.second);
+      this.second = second;
+      System.out.println("[after] second = " + this.second);
+    }
+
+    public Pair<F, S> build() {
+      return new Pair<>(first, second);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index e088bf0..325bdd5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -11,18 +11,11 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 
-import com.android.tools.r8.NeverInline;
-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.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
-import com.android.tools.r8.ir.optimize.classinliner.builders.ControlFlow;
-import com.android.tools.r8.ir.optimize.classinliner.builders.Pair;
-import com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder;
-import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
 import com.android.tools.r8.ir.optimize.classinliner.code.C;
 import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
@@ -40,41 +33,33 @@
 import com.android.tools.r8.ir.optimize.classinliner.trivial.TrivialTestClass;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 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.FoundClassSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import com.google.common.collect.Streams;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.Set;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class ClassInlinerTest extends TestBase {
+public class ClassInlinerTest extends ClassInlinerTestBase {
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public ClassInlinerTest(Backend backend) {
-    this.backend = backend;
+  public ClassInlinerTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
@@ -96,7 +81,7 @@
     };
     String javaOutput = runOnJava(main);
     TestRunResult result =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
             .addKeepMainRule(main)
@@ -112,132 +97,56 @@
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
-        collectTypes(clazz, "testInner", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testInner")));
 
     assertEquals(
         Collections.emptySet(),
-        collectTypes(clazz, "testConstructorMapping1", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testConstructorMapping1")));
 
     assertEquals(
         Collections.emptySet(),
-        collectTypes(clazz, "testConstructorMapping2", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testConstructorMapping2")));
 
     assertEquals(
         Collections.singleton("java.lang.StringBuilder"),
-        collectTypes(clazz, "testConstructorMapping3", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testConstructorMapping3")));
 
     assertEquals(
-        Collections.emptySet(),
-        collectTypes(clazz, "testEmptyClass", "void"));
+        Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("testEmptyClass")));
 
     assertEquals(
         Collections.singleton(
             "com.android.tools.r8.ir.optimize.classinliner.trivial.EmptyClassWithInitializer"),
-        collectTypes(clazz, "testEmptyClassWithInitializer", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testEmptyClassWithInitializer")));
 
     assertEquals(
         Collections.singleton(
             "com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal"),
-        collectTypes(clazz, "testClassWithFinalizer", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testClassWithFinalizer")));
 
     assertEquals(
-        Collections.emptySet(),
-        collectTypes(clazz, "testCallOnIface1", "void"));
+        Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("testCallOnIface1")));
 
     assertEquals(
-        Collections.singleton(
-            "com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
-        collectTypes(clazz, "testCallOnIface2", "void"));
+        Collections.singleton("com.android.tools.r8.ir.optimize.classinliner.trivial.Iface2Impl"),
+        collectTypes(clazz.uniqueMethodWithName("testCallOnIface2")));
 
     assertEquals(
         Sets.newHashSet(
             "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB",
             "java.lang.StringBuilder"),
-        collectTypes(clazz, "testCycles", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testCycles")));
 
     assertEquals(
-        Sets.newHashSet("java.lang.StringBuilder",
+        Sets.newHashSet(
+            "java.lang.StringBuilder",
             "com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB"),
-        collectTypes(inspector.clazz(CycleReferenceAB.class), "foo", "void", "int"));
+        collectTypes(inspector.clazz(CycleReferenceAB.class).uniqueMethodWithName("foo")));
 
     assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
   }
 
   @Test
-  public void testBuilders() throws Exception {
-    Class<?> main = BuildersTestClass.class;
-    Class<?>[] classes = {
-        NeverInline.class,
-        BuildersTestClass.class,
-        BuildersTestClass.Pos.class,
-        Tuple.class,
-        Pair.class,
-        PairBuilder.class,
-        ControlFlow.class,
-    };
-    String javaOutput = runOnJava(main);
-    TestRunResult result =
-        testForR8(backend)
-            .addProgramClasses(classes)
-            .enableInliningAnnotations()
-            .addKeepMainRule(main)
-            .addKeepAttributes("LineNumberTable")
-            .addOptionsModification(
-                o -> {
-                  o.inliningInstructionLimit = 6;
-                  configure(o);
-                })
-            .allowAccessModification()
-            .noMinification()
-            .run(main)
-            .assertSuccessWithOutput(javaOutput);
-
-    CodeInspector inspector = result.inspector();
-    ClassSubject clazz = inspector.clazz(main);
-
-    for (int i = 1; i <= 3; i++) {
-      Set<String> expected =
-          backend == Backend.CF
-              // const-string canonicalization is disabled in CF, which helps ClassInliner identify
-              // PairBuilder as candidate.
-              ? ImmutableSet.of(
-                  "java.lang.StringBuilder",
-                  "com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder")
-              : ImmutableSet.of("java.lang.StringBuilder");
-      assertEquals(expected, collectTypes(clazz, "testSimpleBuilder" + i, "void"));
-    }
-
-    // Note that Pair created instances were also inlined in the following method since
-    // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
-    // as in the above method, the instance of pair would be passed to println() which
-    // would make it not eligible for inlining.
-    // TODO(b/143129517): This relies on PairBuilder::build being inlined, thus the limit of 6.
-    assertEquals(
-        Collections.singleton("java.lang.StringBuilder"),
-        collectTypes(clazz, "testSimpleBuilderWithMultipleBuilds", "void"));
-
-    if (backend == Backend.DEX) {
-      assertFalse(inspector.clazz(PairBuilder.class).isPresent());
-    }
-
-    assertEquals(
-        Collections.singleton("java.lang.StringBuilder"),
-        collectTypes(clazz, "testBuilderConstructors", "void"));
-
-    assertFalse(inspector.clazz(Tuple.class).isPresent());
-
-    assertEquals(
-        Collections.singleton("java.lang.StringBuilder"),
-        collectTypes(clazz, "testWithControlFlow", "void"));
-
-    assertFalse(inspector.clazz(ControlFlow.class).isPresent());
-
-    assertEquals(Collections.emptySet(), collectTypes(clazz, "testWithMoreControlFlow", "void"));
-
-    assertFalse(inspector.clazz(BuildersTestClass.Pos.class).isPresent());
-  }
-
-  @Test
   public void testErroneousInput() throws Exception {
     JasminBuilder builder = new JasminBuilder();
 
@@ -258,7 +167,11 @@
         "  return");
 
     AndroidApp compiled =
-        compileWithR8(builder.build(), getProguardConfig(mainClass.name), this::configure, backend);
+        compileWithR8(
+            builder.build(),
+            getProguardConfig(mainClass.name),
+            this::configure,
+            parameters.getBackend());
 
     // Check that the code fails with an IncompatibleClassChangeError with Java.
     ProcessResult javaResult =
@@ -267,7 +180,7 @@
 
     // Check that the code fails with an IncompatibleClassChangeError with ART.
     ProcessResult result =
-        backend == Backend.DEX
+        parameters.isDexRuntime()
             ? runOnArtRaw(compiled, mainClass.name)
             : runOnJavaRaw(compiled, mainClass.name, Collections.emptyList());
     assertThat(result.stderr, containsString("IncompatibleClassChangeError"));
@@ -284,7 +197,7 @@
     };
     String javaOutput = runOnJava(main);
     TestRunResult result =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableInliningAnnotations()
             .addKeepMainRule(main)
@@ -298,17 +211,11 @@
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(C.class);
 
-    assertEquals(
-        Collections.emptySet(),
-        collectTypes(clazz, "method1", "int"));
+    assertEquals(Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("method1")));
 
-    assertEquals(
-        Collections.emptySet(),
-        collectTypes(clazz, "method2", "int"));
+    assertEquals(Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("method2")));
 
-    assertEquals(
-        Collections.emptySet(),
-        collectTypes(clazz, "method3", "int"));
+    assertEquals(Collections.emptySet(), collectTypes(clazz.uniqueMethodWithName("method3")));
 
     assertFalse(inspector.clazz(C.L.class).isPresent());
     assertFalse(inspector.clazz(C.F.class).isPresent());
@@ -326,7 +233,7 @@
     };
     String javaOutput = runOnJava(main);
     TestRunResult result =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .enableProguardTestOptions()
             .enableInliningAnnotations()
@@ -350,15 +257,15 @@
     // TODO(b/143129517, 141719453): This expectation relies on the class inlining limits.
     assertEquals(
         Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
-        collectTypes(clazz, "testExtraNeverReturnsNormally", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testExtraNeverReturnsNormally")));
 
     assertEquals(
         Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
-        collectTypes(clazz, "testDirectNeverReturnsNormally", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testDirectNeverReturnsNormally")));
 
     assertEquals(
         Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
-        collectTypes(clazz, "testInitNeverReturnsNormally", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testInitNeverReturnsNormally")));
 
     assertThat(inspector.clazz(InvalidRootsTestClass.NeverReturnsNormally.class), isPresent());
     assertThat(
@@ -367,7 +274,7 @@
     // TODO(b/143129517, b/141719453): This expectation relies on the class inlining limits.
     assertEquals(
         Sets.newHashSet("java.lang.StringBuilder", "java.lang.RuntimeException"),
-        collectTypes(clazz, "testRootInvalidatesAfterInlining", "void"));
+        collectTypes(clazz.uniqueMethodWithName("testRootInvalidatesAfterInlining")));
 
     assertThat(inspector.clazz(InvalidRootsTestClass.A.class), not(isPresent()));
     assertThat(inspector.clazz(InvalidRootsTestClass.B.class), not(isPresent()));
@@ -375,7 +282,7 @@
 
   @Test
   public void testDesugaredLambdas() throws Exception {
-    Assume.assumeFalse("No desugaring with CF backend", backend == Backend.CF);
+    Assume.assumeFalse("No desugaring with CF backend", parameters.isCfRuntime());
     Class<?> main = LambdasTestClass.class;
     Class<?>[] classes = {
         LambdasTestClass.class,
@@ -384,7 +291,7 @@
     };
     String javaOutput = runOnJava(main);
     TestRunResult result =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addProgramClasses(classes)
             .addKeepMainRule(main)
             .addKeepAttributes("LineNumberTable")
@@ -404,9 +311,8 @@
     ClassSubject clazz = inspector.clazz(main);
 
     assertEquals(
-        Sets.newHashSet(
-            "java.lang.StringBuilder"),
-        collectTypes(clazz, "testStatelessLambda", "void"));
+        Sets.newHashSet("java.lang.StringBuilder"),
+        collectTypes(clazz.uniqueMethodWithName("testStatelessLambda")));
 
     // TODO(b/120814598): Should only be "java.lang.StringBuilder". Lambdas are not class inlined
     // because parameter usage is not available for each lambda constructor.
@@ -416,9 +322,7 @@
             .map(FoundClassSubject::getFinalName)
             .filter(name -> name.contains(LAMBDA_CLASS_NAME_PREFIX))
             .collect(Collectors.toList()));
-    assertEquals(
-        expectedTypes,
-        collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+    assertEquals(expectedTypes, collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
 
     // TODO(b/120814598): Should be 0. Lambdas are not class inlined because parameter usage is not
     // available for each lambda constructor.
@@ -427,36 +331,6 @@
         inspector.allClasses().stream().filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
   }
 
-  private Set<String> collectTypes(
-      ClassSubject clazz, String methodName, String retValue, String... params) {
-    return Stream.concat(
-        collectNewInstanceTypesWithRetValue(clazz, methodName, retValue, params),
-        collectStaticGetTypesWithRetValue(clazz, methodName, retValue, params)
-    ).collect(Collectors.toSet());
-  }
-
-  private Stream<String> collectNewInstanceTypesWithRetValue(
-      ClassSubject clazz, String methodName, String retValue, String... params) {
-    assertNotNull(clazz);
-    MethodSignature signature = new MethodSignature(methodName, retValue, params);
-    Iterator<InstructionSubject> iterator = clazz.method(signature).iterateInstructions();
-    return Streams.stream(iterator)
-        .filter(InstructionSubject::isNewInstance)
-        .map(is -> ((NewInstanceInstructionSubject) is).getType().toSourceString());
-  }
-
-  private Stream<String> collectStaticGetTypesWithRetValue(
-      ClassSubject clazz, String methodName, String retValue, String... params) {
-    assertNotNull(clazz);
-    MethodSignature signature = new MethodSignature(methodName, retValue, params);
-    Iterator<InstructionSubject> iterator = clazz.method(signature).iterateInstructions();
-    return Streams.stream(iterator)
-        .filter(InstructionSubject::isStaticGet)
-        .map(is -> (FieldAccessInstructionSubject) is)
-        .filter(fais -> fais.holder().is(fais.type()))
-        .map(fais -> fais.holder().toString());
-  }
-
   private String getProguardConfig(String main) {
     return StringUtils.joinLines(
         keepMainProguardConfiguration(main),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTestBase.java
new file mode 100644
index 0000000..71e7aab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTestBase.java
@@ -0,0 +1,46 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestBase;
+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.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public abstract class ClassInlinerTestBase extends TestBase {
+
+  protected Set<String> collectTypes(MethodSubject methodSubject) {
+    assertNotNull(methodSubject);
+    assertThat(methodSubject, isPresent());
+    return Stream.concat(
+            collectNewInstanceTypesWithRetValue(methodSubject),
+            collectStaticGetTypesWithRetValue(methodSubject))
+        .collect(Collectors.toSet());
+  }
+
+  private Stream<String> collectNewInstanceTypesWithRetValue(MethodSubject methodSubject) {
+    return methodSubject
+        .streamInstructions()
+        .filter(InstructionSubject::isNewInstance)
+        .map(is -> ((NewInstanceInstructionSubject) is).getType().toSourceString());
+  }
+
+  private Stream<String> collectStaticGetTypesWithRetValue(MethodSubject methodSubject) {
+    return methodSubject
+        .streamInstructions()
+        .filter(InstructionSubject::isStaticGet)
+        .map(is -> (FieldAccessInstructionSubject) is)
+        .filter(fais -> fais.holder().is(fais.type()))
+        .map(fais -> fais.holder().toString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java
new file mode 100644
index 0000000..1b5d955
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTupleBuilderConstructorsTest.java
@@ -0,0 +1,140 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlinerTupleBuilderConstructorsTest extends ClassInlinerTestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "Tuple1(false, 0, 0, 0, 0, 0, 0.0, 0.0, <null>)",
+          "Tuple1(true, 77, 9977, 35, 42, 987654321123456789, -12.34, 43210.98765, s)");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerTupleBuilderConstructorsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerTupleBuilderConstructorsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+
+    assertEquals(
+        Collections.singleton(StringBuilder.class.getTypeName()), collectTypes(clazz.mainMethod()));
+
+    assertThat(inspector.clazz(Tuple.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new Tuple().myToString());
+      System.out.println(
+          new Tuple(
+                  true,
+                  (byte) 77,
+                  (short) 9977,
+                  '#',
+                  42,
+                  987654321123456789L,
+                  -12.34f,
+                  43210.98765,
+                  "s")
+              .myToString());
+    }
+  }
+
+  static class Tuple {
+
+    boolean z;
+    byte b;
+    short s;
+    char c;
+    int i;
+    long l;
+    float f;
+    double d;
+    Object o;
+
+    Tuple() {}
+
+    Tuple(boolean z, byte b, short s, char c, int i, long l, float f, double d, Object o) {
+      this.z = z;
+      this.b = b;
+      this.s = s;
+      this.c = c;
+      this.i = i;
+      this.l = l;
+      this.f = f;
+      this.d = d;
+      this.o = o;
+    }
+
+    String myToString() {
+      return "Tuple1("
+          + z
+          + ", "
+          + b
+          + ", "
+          + s
+          + ", "
+          + ((int) c)
+          + ", "
+          + i
+          + ", "
+          + l
+          + ", "
+          + f
+          + ", "
+          + d
+          + ", "
+          + (o == null ? "<null>" : o)
+          + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
deleted file mode 100644
index 4a40fe9..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
+++ /dev/null
@@ -1,104 +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.ir.optimize.classinliner.builders;
-
-import com.android.tools.r8.NeverInline;
-
-public class BuildersTestClass {
-  private static int ID = 0;
-
-  private static int nextInt() {
-    return ID++;
-  }
-
-  private static String next() {
-    return Integer.toString(nextInt());
-  }
-
-  public static void main(String[] args) {
-    BuildersTestClass test = new BuildersTestClass();
-    test.testSimpleBuilder1();
-    test.testSimpleBuilderWithMultipleBuilds();
-    test.testBuilderConstructors();
-    test.testWithControlFlow();
-    test.testWithMoreControlFlow();
-  }
-
-  @NeverInline
-  private void testSimpleBuilder1() {
-    System.out.println(
-        new PairBuilder<String, String>().setFirst("f-" + next()).build().myToString());
-    testSimpleBuilder2();
-    testSimpleBuilder3();
-  }
-
-  @NeverInline
-  private void testSimpleBuilder2() {
-    System.out.println(
-        new PairBuilder<String, String>().setSecond("s-" + next()).build().myToString());
-  }
-
-  @NeverInline
-  private void testSimpleBuilder3() {
-    System.out.println(new PairBuilder<String, String>()
-        .setFirst("f-" + next()).setSecond("s-" + next()).build().myToString());
-  }
-
-  @NeverInline
-  private void testSimpleBuilderWithMultipleBuilds() {
-    PairBuilder<String, String> builder = new PairBuilder<>();
-    Pair p1 = builder.build();
-    System.out.println(p1.myToString());
-    builder.setFirst("f-" + next());
-    Pair p2 = builder.build();
-    System.out.println(p2.myToString());
-    builder.setSecond("s-" + next());
-    Pair p3 = builder.build();
-    System.out.println(p3.myToString());
-  }
-
-  @NeverInline
-  private void testBuilderConstructors() {
-    System.out.println(new Tuple().myToString());
-    System.out.println(new Tuple(true, (byte) 77, (short) 9977, '#', 42,
-        987654321123456789L, -12.34f, 43210.98765, "s-" + next() + "-s").myToString());
-  }
-
-  @NeverInline
-  private void testWithControlFlow() {
-    ControlFlow flow = new ControlFlow(-1, 2, 7);
-    for (int k = 0; k < 25; k++) {
-      if (k % 3 == 0) {
-        flow.foo(k);
-      } else if (k % 3 == 1) {
-        flow.bar(nextInt(), nextInt(), nextInt(), nextInt());
-      }
-    }
-    System.out.println("flow = " + flow.toString());
-  }
-
-  @NeverInline
-  private void testWithMoreControlFlow() {
-    String str = "1234567890";
-    Pos pos = new Pos();
-    while (pos.y < str.length()) {
-      pos.x = pos.y;
-      pos.y = pos.x;
-
-      if (str.charAt(pos.x) != '*') {
-        if ('0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
-          while (pos.y < str.length() && '0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
-            pos.y++;
-          }
-        }
-      }
-    }
-  }
-
-  public static class Pos {
-    public int x = 0;
-    public int y = 0;
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java
deleted file mode 100644
index 7b95dc1..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java
+++ /dev/null
@@ -1,54 +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.ir.optimize.classinliner.builders;
-
-public class ControlFlow {
-  int a;
-  int b;
-  int c = 1234;
-  int d;
-  String s = ">";
-
-  ControlFlow(int b, int c, int d) {
-    this.s += this.a++ + ">";
-    this.s += this.b + ">";
-    this.b = b;
-    this.s += this.b + ">";
-    this.s += this.c + ">";
-    this.c += c;
-    this.s += this.c + ">";
-    this.s += (this.d = d) + ">";
-  }
-
-  public void foo(int count) {
-    for (int i = 0; i < count; i++) {
-      switch (i % 4) {
-        case 0:
-          this.s += ++this.a + ">";
-          break;
-        case 1:
-          this.c += this.b;
-          this.s += this.c + ">";
-          break;
-        case 2:
-          this.d += this.d++ + this.c++ + this.b++ + this.a++;
-          this.s += this.d + ">";
-          break;
-      }
-    }
-  }
-
-  public void bar(int a, int b, int c, int d) {
-    this.a += a;
-    this.b += b;
-    this.c += c;
-    this.d += d;
-  }
-
-  @Override
-  public String toString() {
-    return s;
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java
deleted file mode 100644
index fcbea05..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java
+++ /dev/null
@@ -1,21 +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.ir.optimize.classinliner.builders;
-
-public class Pair<F, S> {
-  public final F first;
-  public final S second;
-
-  public Pair(F first, S second) {
-    this.first = first;
-    this.second = second;
-  }
-
-  public String myToString() {
-    return "Pair(" +
-        (first == null ? "<null>" : first) + ", " +
-        (second == null ? "<null>" : second) + ")";
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java
deleted file mode 100644
index 0c80c53..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java
+++ /dev/null
@@ -1,29 +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.ir.optimize.classinliner.builders;
-
-public class PairBuilder<F, S> {
-  public F first;
-  public S second = null;
-
-  public PairBuilder<F, S> setFirst(F first) {
-    System.out.println("[before] first = " + this.first);
-    this.first = first;
-    System.out.println("[after] first = " + this.first);
-    return this;
-  }
-
-  public PairBuilder<F, S> setSecond(S second) {
-    System.out.println("[before] second = " + this.second);
-    this.second = second;
-    System.out.println("[after] second = " + this.second);
-    return this;
-  }
-
-  public Pair<F, S> build() {
-    return new Pair<>(first, second);
-  }
-}
-
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java
deleted file mode 100644
index 1c0718b..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java
+++ /dev/null
@@ -1,38 +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.ir.optimize.classinliner.builders;
-
-public class Tuple {
-  public boolean z;
-  public byte b;
-  public short s;
-  public char c;
-  public int i;
-  public long l;
-  public float f;
-  public double d;
-  public Object o;
-
-  Tuple() {
-  }
-
-  Tuple(boolean z, byte b, short s, char c, int i, long l, float f, double d, Object o) {
-    this.z = z;
-    this.b = b;
-    this.s = s;
-    this.c = c;
-    this.i = i;
-    this.l = l;
-    this.f = f;
-    this.d = d;
-    this.o = o;
-  }
-
-  public String myToString() {
-    return "Tuple1(" + z + ", " + b + ", " + s + ", " +
-        ((int) c) + ", " + i + ", " + l + ", " + f + ", " +
-        d + ", " + (o == null ? "<null>" : o) + ")";
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
deleted file mode 100644
index a3d644f..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationTest.java
+++ /dev/null
@@ -1,136 +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.ir.optimize.membervaluepropagation;
-
-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.assertTrue;
-
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-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.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class InstanceFieldValuePropagationTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  public InstanceFieldValuePropagationTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(InstanceFieldValuePropagationTest.class)
-        .addKeepMainRule(TestClass.class)
-        .enableClassInliningAnnotations()
-        .enableInliningAnnotations()
-        .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(
-            StringUtils.times(StringUtils.lines("A", "42", "Hello world!"), 2));
-  }
-
-  private void inspect(CodeInspector inspector) {
-    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
-    assertThat(testClassSubject, isPresent());
-
-    // Verify that all instance-get instructions in testDefinitelyNotNull() has been removed by
-    // member value propagation.
-    MethodSubject testDefinitelyNotNullMethodSubject =
-        testClassSubject.uniqueMethodWithName("testDefinitelyNotNull");
-    assertThat(testDefinitelyNotNullMethodSubject, isPresent());
-    assertTrue(
-        testDefinitelyNotNullMethodSubject
-            .streamInstructions()
-            .noneMatch(InstructionSubject::isInstanceGet));
-    // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
-    //  ends up being unused.
-    assertTrue(
-        testDefinitelyNotNullMethodSubject
-            .streamInstructions()
-            .anyMatch(InstructionSubject::isNewInstance));
-
-    // Verify that all instance-get instructions in testMaybeNull() has been removed by member value
-    // propagation.
-    MethodSubject testMaybeNullMethodSubject =
-        testClassSubject.uniqueMethodWithName("testMaybeNull");
-    assertThat(testMaybeNullMethodSubject, isPresent());
-    // TODO(b/125282093): Should synthesize a null-check and still propagate the field values even
-    //  when the receiver is nullable.
-    assertTrue(
-        testMaybeNullMethodSubject
-            .streamInstructions()
-            .anyMatch(InstructionSubject::isInstanceGet));
-    // TODO(b/125282093): Should be able to remove the new-instance instruction since the instance
-    //  ends up being unused.
-    assertTrue(
-        testMaybeNullMethodSubject
-            .streamInstructions()
-            .anyMatch(InstructionSubject::isNewInstance));
-
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-    // TODO(b/125282093): Need to remove the instance-put instructions in A.<init>(). This can not
-    //  be done safely by the time we process A.<init>(), so some kind of post-processing is needed.
-    assertEquals(3, aClassSubject.allInstanceFields().size());
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      testDefinitelyNotNull();
-      testMaybeNull();
-    }
-
-    @NeverInline
-    static void testDefinitelyNotNull() {
-      A a = new A();
-      System.out.println(a.e);
-      System.out.println(a.i);
-      System.out.println(a.s);
-    }
-
-    @NeverInline
-    static void testMaybeNull() {
-      A a = System.currentTimeMillis() >= 0 ? new A() : null;
-      System.out.println(a.e);
-      System.out.println(a.i);
-      System.out.println(a.s);
-    }
-  }
-
-  @NeverClassInline
-  static class A {
-
-    MyEnum e = MyEnum.A;
-    int i = 42;
-    String s = "Hello world!";
-  }
-
-  enum MyEnum {
-    A,
-    B
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
deleted file mode 100644
index d2b739e..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
+++ /dev/null
@@ -1,87 +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.ir.optimize.membervaluepropagation;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-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.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class InstanceFieldValuePropagationWithMultipleInstanceInitializersTest extends TestBase {
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  public InstanceFieldValuePropagationWithMultipleInstanceInitializersTest(
-      TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.class)
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
-  }
-
-  private void inspect(CodeInspector inspector) {
-    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
-    assertThat(testClassSubject, isPresent());
-
-    // Verify that the instance-get instruction in main() is still present in main(), since the
-    // value of `a.greeting` depends on the constructor being used.
-    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
-    assertThat(mainMethodSubject, isPresent());
-    assertTrue(mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isInstanceGet));
-
-    // Verify that the `greeting` field is still present.
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-    assertThat(aClassSubject.uniqueFieldWithName("greeting"), isPresent());
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      A a = System.currentTimeMillis() >= 0 ? new A() : new A(new Object());
-      System.out.println(a.greeting);
-    }
-  }
-
-  @NeverClassInline
-  static class A {
-
-    String greeting;
-
-    A() {
-      this.greeting = "Hello world!";
-    }
-
-    A(Object unused) {
-      this.greeting = ":-(";
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
index 1907f33..44dc04c 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
@@ -5,7 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.CompilationFailedException;
 import com.google.common.collect.ImmutableList;
 import org.junit.Rule;
 import org.junit.Test;
@@ -71,11 +71,10 @@
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
 
-    // TODO(zerny): Should we fail early on the above code? Art fails with a verification error
-    // because Test.foo is expected to be in the direct method table.
-    if (ToolHelper.artSupported()) {
-      thrown.expect(AssertionError.class);
-    }
+    thrown.expect(CompilationFailedException.class);
+
+    // TODO(b/110175213): This will fail with a compilation exception since we cannot translate
+    //  an invoke-special to a member on the same class.
     String artResult = runOnArtD8(builder, clazz.name);
     assertEquals(expected, artResult);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
index e42b66b..c7ba86e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
@@ -57,7 +57,6 @@
     ProcessResult processResult =
         ToolHelper.runKotlinc(
             null,
-            null,
             supertypeLibJar,
             null,
             getKotlinFileInTest(supertypeLibFolder, "impl"),
@@ -70,7 +69,6 @@
     processResult =
         ToolHelper.runKotlinc(
             null,
-            null,
             extLibJar,
             null,
             getKotlinFileInTest(extLibFolder, "B")
@@ -102,26 +100,26 @@
       // API entry is kept, hence the presence of Metadata.
       DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
       assertNotNull(metadata);
-      // TODO(b/143687784): test its metadata doesn't point to shrunken itf.
+      assertThat(metadata.toString(), not(containsString("internal")));
+      assertThat(metadata.toString(), not(containsString("Itf")));
     });
 
     Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
     compileResult.writeToZip(r8ProcessedLibZip);
 
     String appFolder = PKG_PREFIX + "/supertype_app";
-    ProcessResult kotlinTestCompileResult =
+    Path output =
         kotlinc(parameters.getRuntime().asCf())
             .addClasspathFiles(r8ProcessedLibZip)
             .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
-            // TODO(b/143687784): update to just .compile() once fixed.
             .setOutputPath(temp.newFolder().toPath())
-            .compileRaw();
+            .compile();
 
-    // TODO(b/143687784): should be able to compile!
-    assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString("unresolved supertypes: " + pkg + ".supertype_lib.internal.Itf"));
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), r8ProcessedLibZip)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), pkg + ".supertype_app.MainKt")
+        .assertSuccessWithOutputLines("Impl::foo", "Program::foo");
   }
 
   @Test
@@ -152,7 +150,7 @@
       // API entry is kept, hence the presence of Metadata.
       DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
       assertNotNull(metadata);
-      // TODO(b/143687784): test its metadata doesn't point to shrunken Super.
+      assertThat(metadata.toString(), not(containsString("Super")));
     });
 
     Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
@@ -168,9 +166,6 @@
             .compileRaw();
     // TODO(b/143687784): should be able to compile!
     assertNotEquals(0, kotlinTestCompileResult.exitCode);
-    assertThat(
-        kotlinTestCompileResult.stderr,
-        containsString("unresolved supertypes: " + pkg + ".extension_lib.Super"));
     assertThat(kotlinTestCompileResult.stderr, containsString("unresolved reference: doStuff"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
index d028564..59b273a 100644
--- a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
+++ b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
@@ -7,6 +7,7 @@
 import static junit.framework.TestCase.assertEquals;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -31,6 +32,7 @@
     }
   }
 
+  @NeverClassInline
   public static class B extends A {
 
     public int f0;
@@ -48,6 +50,7 @@
     }
   }
 
+  @NeverClassInline
   public static class C extends A {
 
     public int f0;
@@ -77,7 +80,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public FieldNamingObfuscationDictionaryTest(TestParameters parameters) {
@@ -94,7 +97,8 @@
         .addInnerClasses(FieldNamingObfuscationDictionaryTest.class)
         .addKeepRules("-overloadaggressively", "-obfuscationdictionary " + dictionary.toString())
         .addKeepMainRule(Runner.class)
-        .setMinApi(parameters.getRuntime())
+        .enableClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Runner.class, "HELLO", "WORLD")
         .assertSuccessWithOutputLines("2HELLO WORLD", "2HELLO WORLD")
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
index b2ed534..ebd7420 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
@@ -26,7 +26,6 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
@@ -44,11 +43,6 @@
 
   @ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
 
-  @BeforeParam
-  public static void forceCompilation(TestParameters parameters) {
-    compilation.apply(parameters.getBackend());
-  }
-
   private static R8TestCompileResult compile(Backend backend)
       throws com.android.tools.r8.CompilationFailedException, IOException, ExecutionException {
     R8TestCompileResult compileResult =
@@ -86,7 +80,7 @@
 
   @Test
   public void test() throws Throwable {
-    compile(parameters.getBackend())
+    compilation.apply(parameters.getBackend())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
index 98780e9..7fa0543 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
@@ -31,7 +31,6 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
 
 @RunWith(Parameterized.class)
 public class ApplyMappingAfterVerticalMergingMethodTest extends TestBase {
@@ -100,11 +99,6 @@
   @ClassRule
   public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
 
-  @BeforeParam
-  public static void forceCompilation(TestParameters parameters) {
-    compilationResults.apply(parameters.getBackend());
-  }
-
   public static CompilationResult compile(Backend backend)
       throws ExecutionException, CompilationFailedException, IOException {
     R8TestCompileResult library = compileLibrary(backend);
diff --git a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
index 42a64b5..d3604b6 100644
--- a/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
+++ b/src/test/java/com/android/tools/r8/regress/b142682636/Regress142682636Runner.java
@@ -8,20 +8,38 @@
 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 com.android.tools.r8.code.MoveWide;
 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.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 Regress142682636Runner extends TestBase {
-  private final Class<?> testClass = Regress142682636.class;
+  private static final Class<?> testClass = Regress142682636.class;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public Regress142682636Runner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
 
   @Test
   public void test() throws Exception {
     CodeInspector inspector = testForD8()
         .addProgramClasses(testClass)
+        .setMinApi(parameters.getApiLevel())
         .release()
         .compile()
         .inspector();
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
index 4dff5f4..c9d94ad 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeInterfaceOnClassTest.java
@@ -4,24 +4,19 @@
 package com.android.tools.r8.resolution;
 
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
-import com.android.tools.r8.CompilationFailedException;
 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.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.references.Reference;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.util.ASMifier;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfaceOnClassTest extends TestBase {
@@ -39,54 +34,49 @@
 
   @Test
   public void testReference() throws Exception {
-    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+    testForRuntime(parameters)
         .addProgramClasses(I.class, C1.class, C2.class)
-        .addProgramClassFileData(DumpMain.dump())
+        .addProgramClassFileData(transformMain())
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(false));
   }
 
   @Test
   public void testR8() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addProgramClasses(I.class, C1.class, C2.class)
-          .addProgramClassFileData(DumpMain.dump())
-          .addKeepMainRule(Main.class)
-          .setMinApi(parameters.getApiLevel())
-          .compile()
-          .run(parameters.getRuntime(), Main.class)
-          .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
-    } catch (CompilationFailedException e) {
-      // TODO(b/144085169): The class file pipeline throws an assertion error, but should not.
-      assertTrue(parameters.isCfRuntime());
-    }
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, C1.class, C2.class)
+        .addProgramClassFileData(transformMain())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.testing.allowInvokeErrors = true)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
   }
 
   private Matcher<String> getExpectedFailureMatcher(boolean isR8) {
-    if (parameters.getRuntime().isDex()
-        && parameters
-            .getRuntime()
-            .asDex()
-            .getVm()
-            .getVersion()
-            .isOlderThanOrEqual(Version.V4_4_4)) {
+    // Old runtimes are implemented to throw the wrong error, so NoSuchMethodError is expected.
+    if (isDexVmOlderThanOrEqualTo(Version.V4_4_4)) {
       return containsString("NoSuchMethodError");
     }
-    if (isR8
-        && parameters.getRuntime().isDex()
-        && parameters
-            .getRuntime()
-            .asDex()
-            .getVm()
-            .getVersion()
-            .isOlderThanOrEqual(Version.V6_0_1)) {
-      // TODO(b/144085169): R8 ends up causing a code change changing the error on these runtimes.
+    // For 5 and 6, the error is correct, but only as long as the class has a non-abstract method.
+    // R8 will not trace the C1.f and C2.f as the resolution of I.f fails. The implementation
+    // methods are removed and this again causes the runtime to throw the wrong error.
+    if (isR8 && isDexVmOlderThanOrEqualTo(Version.V6_0_1)) {
       return containsString("NoSuchMethodError");
     }
     return containsString("IncompatibleClassChangeError");
   }
 
+  private boolean isDexVmOlderThanOrEqualTo(Version version) {
+    return parameters.getRuntime().isDex()
+        && parameters
+            .getRuntime()
+            .asDex()
+            .getVm()
+            .getVersion()
+            .isOlderThanOrEqual(version);
+  }
+
   public abstract static class I {
     public abstract void f();
   }
@@ -115,91 +105,19 @@
     }
   }
 
-  static class DumpMain implements Opcodes {
-
-    public static void main(String[] args) throws Exception {
-      ASMifier.main(
-          new String[] {"-debug", ToolHelper.getClassFileForTestClass(Main.class).toString()});
-    }
-
-    public static byte[] dump() {
-
-      ClassWriter classWriter = new ClassWriter(0);
-      MethodVisitor methodVisitor;
-
-      classWriter.visit(
-          V1_8,
-          ACC_SUPER,
-          DescriptorUtils.getBinaryNameFromJavaType(Main.class.getName()),
-          null,
-          "java/lang/Object",
-          null);
-
-      {
-        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(1, 1);
-        methodVisitor.visitEnd();
-      }
-      {
-        methodVisitor =
-            classWriter.visitMethod(
-                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
-        methodVisitor.visitCode();
-        methodVisitor.visitVarInsn(ALOAD, 0);
-        methodVisitor.visitInsn(ARRAYLENGTH);
-        methodVisitor.visitInsn(ICONST_2);
-        methodVisitor.visitInsn(IREM);
-        Label label0 = new Label();
-        methodVisitor.visitJumpInsn(IFNE, label0);
-        methodVisitor.visitTypeInsn(
-            NEW, "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C1");
-        methodVisitor.visitInsn(DUP);
-        methodVisitor.visitMethodInsn(
-            INVOKESPECIAL,
-            "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C1",
-            "<init>",
-            "()V",
-            false);
-        Label label1 = new Label();
-        methodVisitor.visitJumpInsn(GOTO, label1);
-        methodVisitor.visitLabel(label0);
-        methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-        methodVisitor.visitTypeInsn(
-            NEW, "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C2");
-        methodVisitor.visitInsn(DUP);
-        methodVisitor.visitMethodInsn(
-            INVOKESPECIAL,
-            "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$C2",
-            "<init>",
-            "()V",
-            false);
-        methodVisitor.visitLabel(label1);
-        methodVisitor.visitFrame(
-            Opcodes.F_SAME1,
-            0,
-            null,
-            1,
-            new Object[] {"com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$I"});
-        methodVisitor.visitVarInsn(ASTORE, 1);
-        methodVisitor.visitVarInsn(ALOAD, 1);
-        // Changed INVOKEVIRTUAL & false => INVOKEINTERFACE & true.
-        methodVisitor.visitMethodInsn(
-            INVOKEINTERFACE,
-            "com/android/tools/r8/resolution/InvokeInterfaceOnClassTest$I",
-            "f",
-            "()V",
-            true);
-        methodVisitor.visitInsn(RETURN);
-        methodVisitor.visitMaxs(2, 2);
-        methodVisitor.visitEnd();
-      }
-      classWriter.visitEnd();
-
-      return classWriter.toByteArray();
-    }
+  private static byte[] transformMain() throws Exception {
+    String binaryNameForI = Reference.classFromClass(I.class).getBinaryName();
+    return transformer(Main.class)
+        .transformMethodInsnInMethod("main",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (owner.equals(binaryNameForI) && name.equals("f")) {
+                assertEquals(Opcodes.INVOKEVIRTUAL, opcode);
+                assertFalse(isInterface);
+                continuation.apply(Opcodes.INVOKEINTERFACE, owner, name, descriptor, true);
+              } else {
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
index 6c2effe..d0a4906 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeVirtualOnInterfaceTest.java
@@ -9,7 +9,6 @@
 import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -36,7 +35,7 @@
 
   @Test
   public void testReference() throws Exception {
-    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+    testForRuntime(parameters)
         .addProgramClasses(I.class, C1.class, C2.class)
         .addProgramClassFileData(transformMain())
         .run(parameters.getRuntime(), Main.class)
@@ -45,45 +44,40 @@
 
   @Test
   public void testR8() throws Exception {
-    try {
-      testForR8(parameters.getBackend())
-          .addProgramClasses(I.class, C1.class, C2.class)
-          .addProgramClassFileData(transformMain())
-          .addKeepMainRule(Main.class)
-          .setMinApi(parameters.getApiLevel())
-          .compile()
-          .run(parameters.getRuntime(), Main.class)
-          .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
-    } catch (CompilationFailedException e) {
-      // TODO(b/144085169): The class file pipeline throws an assertion error, but should not.
-      assertTrue(parameters.isCfRuntime());
-    }
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, C1.class, C2.class)
+        .addProgramClassFileData(transformMain())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.testing.allowInvokeErrors = true)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(getExpectedFailureMatcher(true));
   }
 
   private Matcher<String> getExpectedFailureMatcher(boolean isR8) {
-    if (parameters.getRuntime().isDex()
-        && parameters
-            .getRuntime()
-            .asDex()
-            .getVm()
-            .getVersion()
-            .isOlderThanOrEqual(Version.V4_4_4)) {
+    // Old runtimes fail verification outright.
+    if (isDexVmOlderThanOrEqualTo(Version.V4_4_4)) {
       return containsString("VerifyError");
     }
-    if (isR8
-        && parameters.getRuntime().isDex()
-        && parameters
-            .getRuntime()
-            .asDex()
-            .getVm()
-            .getVersion()
-            .isOlderThanOrEqual(Version.V7_0_0)) {
-      // TODO(b/144085169): R8 ends up causing a code change changing the error on these runtimes.
+    // For 5, 6 and 7, the error is correct, but only if the class has a non-abstract method.
+    // R8 will not trace the C1.f and C2.f as the resolution of I.f fails. The implementation
+    // methods are removed and this again causes the runtime to throw the wrong error.
+    if (isR8 && isDexVmOlderThanOrEqualTo(Version.V7_0_0)) {
       return containsString("NoSuchMethodError");
     }
     return containsString("IncompatibleClassChangeError");
   }
 
+  private boolean isDexVmOlderThanOrEqualTo(Version version) {
+    return parameters.getRuntime().isDex()
+        && parameters
+        .getRuntime()
+        .asDex()
+        .getVm()
+        .getVersion()
+        .isOlderThanOrEqual(version);
+  }
+
   public interface I {
     void f();
   }
@@ -121,9 +115,9 @@
               if (owner.equals(binaryNameForI) && name.equals("f")) {
                 assertEquals(INVOKEINTERFACE, opcode);
                 assertTrue(isInterface);
-                continuation.visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, false);
+                continuation.apply(INVOKEVIRTUAL, owner, name, descriptor, false);
               } else {
-                continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
               }
             })
         .transform();
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index ef0e967..bae2760 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -101,7 +101,6 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/144085169) R8 masks ICCE.
-        .assertSuccessWithOutput(EXPECTED.replace("ICCE", "InterfaceWithDefault"));
+        .assertSuccessWithOutput(EXPECTED);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index a2df4aa..ec01f62 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -268,7 +268,7 @@
   public void lookupSingleTarget() {
     DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
     Assert.assertNotNull(
-        appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve());
+        appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     DexEncodedMethod singleVirtualTarget = appInfo.lookupSingleVirtualTarget(method, method.holder);
     if (singleTargetHolderOrNull == null) {
       Assert.assertNull(singleVirtualTarget);
@@ -283,7 +283,7 @@
   public void lookupVirtualTargets() {
     DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
     Assert.assertNotNull(
-        appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve());
+        appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).getSingleTarget());
     ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
     if (resolutionResult.isValidVirtualTarget(appInfo.app().options)) {
       Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 6e87ef3..f3191ac 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -133,7 +133,7 @@
   @Test
   public void lookupSingleTarget() {
     DexEncodedMethod resolved =
-        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB).asResultOfResolve();
+        appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB).getSingleTarget();
     assertEquals(methodOnA, resolved.method);
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index 968f21e..e876ce0 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -178,7 +178,7 @@
   @Test
   public void lookupVirtualTargets() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
-    DexEncodedMethod resolved = resolutionResult.asResultOfResolve();
+    DexEncodedMethod resolved = resolutionResult.getSingleTarget();
     assertEquals(methodOnA, resolved.method);
     assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index f5a99fa..30295fd 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Collections;
@@ -65,7 +64,7 @@
         .addProgramClasses(CLASSES)
         .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
+        .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
   }
 
   @Test
@@ -75,16 +74,13 @@
         .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
+        .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
   }
 
-  private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
-    if (isR8
-        && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
-      // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
-      return containsString("NullPointerException");
-    }
+  private Matcher<String> getExpectedErrorMatcher() {
     if (parameters.isDexRuntime()
         && parameters
             .getRuntime()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index bd2d4ac..6c4070e 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.resolution.SingleTargetLookupTest;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Collections;
@@ -65,7 +64,7 @@
         .addProgramClasses(CLASSES)
         .addProgramClassFileData(transformB())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
+        .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
   }
 
   @Test
@@ -75,16 +74,13 @@
         .addProgramClassFileData(transformB())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
+        .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
   }
 
-  private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
-    if (isR8
-        && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
-      // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
-      return containsString("NullPointerException");
-    }
+  private Matcher<String> getExpectedErrorMatcher() {
     if (parameters.isDexRuntime()
         && parameters
             .getRuntime()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index af19bf1..581554b 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -5,7 +5,6 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
@@ -58,7 +57,6 @@
     resolutionResult
         .asFailedResolution()
         .forEachFailureDependency(
-            clazz -> fail("Unexpected class dependency"),
             target -> holders.add(target.method.holder.toSourceString()));
     assertEquals(ImmutableSet.of(L.class.getTypeName(), R.class.getTypeName()), holders);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index 13de4d4..47d7d65 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -5,13 +5,11 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 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.TestRuntime;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
@@ -60,7 +58,6 @@
     resolutionResult
         .asFailedResolution()
         .forEachFailureDependency(
-            clazz -> fail("Unexpected class dependency"),
             m -> holders.add(m.method.holder.toSourceString()));
     assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
   }
@@ -82,16 +79,7 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .apply(r -> checkResultR8(r));
-  }
-
-  private void checkResultR8(TestRunResult<?> runResult) {
-    // TODO(b/144085169): R8/CF produces incorrect result.
-    if (parameters.getRuntime().isCf()) {
-      runResult.assertFailureWithErrorThatMatches(containsString("NullPointerException"));
-    } else {
-      runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-    }
+        .assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
   }
 
   public interface I {
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index ab80fe5..298134a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -32,7 +32,6 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ClassWriter;
@@ -179,11 +178,6 @@
 
   @ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
 
-  @BeforeParam
-  public static void forceCompilation(TestParameters parameters) {
-    compilationResults.apply(parameters.getBackend());
-  }
-
   private static Function<Backend, CompilationResults> compilationResults =
       memoizeFunction(RemoveAssertionsTest::compileAll);
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index 9da1f4a..5d110e4 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -82,7 +82,6 @@
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inlined"), 1);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("inSwitch"), 11);
       assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), 1);
-      assertOrdinalReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), 1);
     } else {
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -90,12 +89,12 @@
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("inSwitch"));
       assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
-      assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("phi"));
+    assertOrdinalWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   @Test
@@ -126,14 +125,12 @@
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("multipleUsages"), expectedConst);
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("inlined"), "TWO");
       assertNameReplacedWithConst(clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
-      assertNameReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
     } else {
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("simple"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("local"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("multipleUsages"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
       assertNameWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
-      assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     // TODO(jakew) this should be allowed!
@@ -141,6 +138,7 @@
 
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
     assertNameWasNotReplaced(clazz.uniqueMethodWithName("phi"));
+    assertNameWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   @Test
@@ -180,7 +178,6 @@
       assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonValueStaticField"), "TWO");
       assertToStringReplacedWithConst(
           clazz.uniqueMethodWithName("differentTypeStaticField"), "DOWN");
-      assertToStringReplacedWithConst(clazz.uniqueMethodWithName("nonStaticGet"), "TWO");
     } else {
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("noToString"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("local"));
@@ -188,11 +185,11 @@
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("inlined"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonValueStaticField"));
       assertToStringWasNotReplaced(clazz.uniqueMethodWithName("differentTypeStaticField"));
-      assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
     }
 
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("libraryType"));
     assertToStringWasNotReplaced(clazz.uniqueMethodWithName("phi"));
+    assertToStringWasNotReplaced(clazz.uniqueMethodWithName("nonStaticGet"));
   }
 
   private static void assertOrdinalReplacedWithConst(MethodSubject method, int expectedConst) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
index 8f09a80..2ecc9eb 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/Names.java
@@ -77,10 +77,12 @@
     return number.name();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private static String nonStaticGet() {
     return new Names().two.name();
   }
+
   private final Number two = Number.TWO;
 
   public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
index 3617c8b..8055344 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/ToStrings.java
@@ -115,10 +115,12 @@
     return number.toString();
   }
 
+  @AssumeMayHaveSideEffects
   @NeverInline
   private static String nonStaticGet() {
     return new ToStrings().two.toString();
   }
+
   private final NoToString two = NoToString.TWO;
 
   public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
index 92d16ae..ea96ead 100644
--- a/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EffectivelyFinalInstanceFieldsTest.java
@@ -56,50 +56,43 @@
         .enableMergeAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
-        .inspect(
-            codeInspector -> {
-              ClassSubject main = codeInspector.clazz(MAIN);
-              assertThat(main, isPresent());
+        .inspect(codeInspector -> {
+          ClassSubject main = codeInspector.clazz(MAIN);
+          assertThat(main, isPresent());
 
-              MethodSubject mainMethod = main.mainMethod();
-              assertThat(mainMethod, isPresent());
+          MethodSubject mainMethod = main.mainMethod();
+          assertThat(mainMethod, isPresent());
 
-              assertTrue(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
-              assertTrue(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
-              // TODO(b/138913138): not trivial; assigned only once in <init>
-              assertFalse(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
-              assertTrue(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
-              assertTrue(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
-              // TODO(b/138913138): not trivial; assigned multiple times, but within a certain
-              // range.
-              assertFalse(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
-              assertTrue(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
-              assertTrue(
-                  mainMethod
-                      .streamInstructions()
-                      .noneMatch(i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
-            })
+          assertTrue(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 1", JumboStringMode.ALLOW)));
+          // TODO(b/138913138): effectively final, and default value is set.
+          assertFalse(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 2", JumboStringMode.ALLOW)));
+          // TODO(b/138913138): not trivial; assigned only once in <init>
+          assertFalse(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 3", JumboStringMode.ALLOW)));
+          assertTrue(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 4", JumboStringMode.ALLOW)));
+          // TODO(b/138913138): effectively final, and default value is set.
+          assertFalse(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 5", JumboStringMode.ALLOW)));
+          // TODO(b/138913138): not trivial; assigned multiple times, but within a certain range.
+          assertFalse(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 6", JumboStringMode.ALLOW)));
+          assertTrue(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 7", JumboStringMode.ALLOW)));
+          // TODO(b/138913138): effectively final, and default value is set.
+          assertFalse(
+              mainMethod.streamInstructions().noneMatch(
+                  i -> i.isConstString("Dead code: 8", JumboStringMode.ALLOW)));
+        })
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("The end");
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 287b736..5220c01 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -35,7 +35,6 @@
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.BeforeParam;
 
 @RunWith(Parameterized.class)
 public class NonVirtualOverrideTest extends TestBase {
@@ -91,14 +90,6 @@
 
   @ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
 
-  @BeforeParam
-  public static void forceCompilation(
-      TestParameters parameters, boolean enableClassInlining, boolean enableVerticalClassMerging) {
-    expectedResults.apply(isDexVmBetween5_1_1and7_0_0(parameters));
-    compilationResults.apply(
-        new Dimensions(parameters.getBackend(), enableClassInlining, enableVerticalClassMerging));
-  }
-
   private static Function<Boolean, String> expectedResults =
       memoizeFunction(NonVirtualOverrideTest::getExpectedResult);
 
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
index b17fae6..4ea48cb 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
@@ -9,6 +9,8 @@
 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.ToolHelper;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -16,14 +18,29 @@
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 class PrintConfigurationTestClass {
 
   public static void main(String[] args) {}
 }
 
+@RunWith(Parameterized.class)
 public class PrintConfigurationTest extends TestBase {
 
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public PrintConfigurationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void testSingleConfigurationWithAbsolutePath() throws Exception {
     Path printConfigurationFile = temp.newFile().toPath();
@@ -50,7 +67,7 @@
             "-printconfiguration proguard-config-out.txt");
     FileUtils.writeTextFile(proguardConfigFile, proguardConfig.trim());
 
-    testForExternalR8(Backend.DEX)
+    testForExternalR8(Backend.DEX, parameters.getRuntime())
         .addProgramClasses(PrintConfigurationTestClass.class)
         .addKeepRuleFiles(proguardConfigFile)
         .compile();
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
index 7c36a88..8c782f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/dflt/DefaultMethodsTest.java
@@ -166,7 +166,8 @@
       CodeInspector inspector,
       Set<String> expected,
       boolean interfaceMethodsKept,
-      Shrinker shrinker) {
+      Shrinker shrinker,
+      Set<String> subSubImplExpected) {
     ClassSubject superIfaceSubject = inspector.clazz(SuperIface.class);
     assertThat(superIfaceSubject, isPresent());
     if (interfaceMethodsKept) {
@@ -210,48 +211,71 @@
     assertEquals(expected.contains("m5"), subImplSubject.uniqueMethodWithName("m5").isPresent());
     ClassSubject subSubImplSubject = inspector.clazz(SubSubImpl.class);
     assertThat(subSubImplSubject, isPresent());
-    assertThat(subSubImplSubject.uniqueMethodWithName("m1"), not(isPresent()));
-    assertThat(subSubImplSubject.uniqueMethodWithName("m2"), not(isPresent()));
-    assertThat(subSubImplSubject.uniqueMethodWithName("m3"), not(isPresent()));
-    assertThat(subSubImplSubject.uniqueMethodWithName("m4"), not(isPresent()));
-    assertThat(subSubImplSubject.uniqueMethodWithName("m5"), not(isPresent()));
-    assertEquals(expected.contains("m6"), subSubImplSubject.uniqueMethodWithName("m6").isPresent());
+    // FOO
+    assertEquals(
+        subSubImplExpected.contains("m1"),
+        subSubImplSubject.uniqueMethodWithName("m1").isPresent());
+    assertEquals(
+        subSubImplExpected.contains("m2"),
+        subSubImplSubject.uniqueMethodWithName("m2").isPresent());
+    assertEquals(
+        subSubImplExpected.contains("m3"),
+        subSubImplSubject.uniqueMethodWithName("m3").isPresent());
+    assertEquals(
+        subSubImplExpected.contains("m4"),
+        subSubImplSubject.uniqueMethodWithName("m4").isPresent());
+    assertEquals(
+        subSubImplExpected.contains("m5"),
+        subSubImplSubject.uniqueMethodWithName("m5").isPresent());
+    assertEquals(
+        subSubImplExpected.contains("m6"),
+        subSubImplSubject.uniqueMethodWithName("m6").isPresent());
   }
 
   private void checkAllMethodsInterfacesKept(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3", "m4", "m5", "m6"), true, shrinker);
+    checkMethods(
+        inspector,
+        ImmutableSet.of("m1", "m2", "m3", "m4", "m5"),
+        true,
+        shrinker,
+        ImmutableSet.of("m6"));
   }
 
-  private void checkAllMethodsInterfacesNotKept(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m1", "m2", "m3", "m4", "m5", "m6"), false, shrinker);
+  private void checkAllMethodsInterfacesAreKeptOnClass(CodeInspector inspector, Shrinker shrinker) {
+    checkMethods(
+        inspector,
+        ImmutableSet.of("m1", "m2", "m3", "m4", "m5"),
+        false,
+        shrinker,
+        ImmutableSet.of("m1", "m2", "m3", "m6"));
   }
 
   private void checkOnlyM1(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m1"), true, shrinker);
+    checkMethods(inspector, ImmutableSet.of("m1"), true, shrinker, ImmutableSet.of());
   }
 
   private void checkOnlyM1InterfacesNotKept(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m1"), false, shrinker);
+    checkMethods(inspector, ImmutableSet.of("m1"), false, shrinker, ImmutableSet.of("m1"));
   }
 
   private void checkOnlyM2(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m2"), true, shrinker);
+    checkMethods(inspector, ImmutableSet.of("m2"), true, shrinker, ImmutableSet.of());
   }
 
   private void checkOnlyM3(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m3"), true, shrinker);
+    checkMethods(inspector, ImmutableSet.of("m3"), true, shrinker, ImmutableSet.of());
   }
 
   private void checkOnlyM4(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m4"), true, shrinker);
+    checkMethods(inspector, ImmutableSet.of("m4"), true, shrinker, ImmutableSet.of());
   }
 
   private void checkOnlyM5(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m5"), true, shrinker);
+    checkMethods(inspector, ImmutableSet.of("m5"), true, shrinker, ImmutableSet.of());
   }
 
   private void checkOnlyM6(CodeInspector inspector, Shrinker shrinker) {
-    checkMethods(inspector, ImmutableSet.of("m6"), true, shrinker);
+    checkMethods(inspector, ImmutableSet.of(), true, shrinker, ImmutableSet.of("m6"));
   }
 
   public String allMethodsOutput() {
@@ -290,9 +314,9 @@
         "SubImpl.m3 not found",
         "SubImpl.m4 not found",
         "SubImpl.m5 found",
-        "SubSubImpl.m1 not found",
-        "SubSubImpl.m2 not found",
-        "SubSubImpl.m3 not found",
+        "SubSubImpl.m1 found",
+        "SubSubImpl.m2 found",
+        "SubSubImpl.m3 found",
         "SubSubImpl.m4 not found",
         "SubSubImpl.m5 not found",
         "SubSubImpl.m6 found");
@@ -334,7 +358,7 @@
         "SubImpl.m3 not found",
         "SubImpl.m4 not found",
         "SubImpl.m5 not found",
-        "SubSubImpl.m1 not found",
+        "SubSubImpl.m1 found",
         "SubSubImpl.m2 not found",
         "SubSubImpl.m3 not found",
         "SubSubImpl.m4 not found",
@@ -466,7 +490,7 @@
     // interfaces with the default methods are not explicitly kept?
     runTest(
         "-keep class **.SubSubImpl { *; }",
-        this::checkAllMethodsInterfacesNotKept,
+        this::checkAllMethodsInterfacesAreKeptOnClass,
         allMethodsOutputInterfacesNotKept());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index ecfc895..62ec020 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -10,10 +10,14 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
@@ -47,15 +51,20 @@
 
   @Test
   public void testStaticMethod() throws Exception {
-    test(FooStaticMethod.class, TestStaticMethod.class);
+    test(FooStaticMethod.class, TestStaticMethod.class, null);
   }
 
   @Test
   public void testStaticField() throws Exception {
-    test(FooStaticField.class, TestStaticField.class);
+    test(
+        FooStaticField.class,
+        TestStaticField.class,
+        builder -> builder.enableInliningAnnotations().enableMemberValuePropagationAnnotations());
   }
 
-  private void test(Class<?> fooClass, Class<?> testClass) throws Exception {
+  private void test(
+      Class<?> fooClass, Class<?> testClass, ThrowableConsumer<R8FullTestBuilder> configuration)
+      throws Exception {
     WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(null);
     GraphInspector inspector =
         testForR8(parameters.getBackend())
@@ -63,6 +72,7 @@
             .enableGraphInspector(whyAreYouKeepingConsumer)
             .addProgramClasses(testClass, fooClass)
             .addKeepMainRule(testClass)
+            .apply(configuration)
             .run(parameters.getRuntime(), testClass)
             .assertSuccessWithOutput(EXPECTED)
             .graphInspector();
@@ -137,6 +147,10 @@
 
     private FooStaticField() {}
 
+    // Ensure that toString() remains in TestStaticField.main(). Otherwise the expression
+    // `new TestStaticField().foo.toString()` can be optimized into "Foo!".
+    @NeverInline
+    @NeverPropagateValue
     @Override
     public String toString() {
       return "Foo!";
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
index 442fda4..2961a50 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
@@ -10,11 +10,11 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
@@ -50,26 +50,29 @@
         .addKeepMethodRules(J.class, "void foo()")
         .addKeepRules("-dontwarn")
         .compile()
-        .inspect(AbstractInterfaceMethodsTest::inspectBaseInterfaceRemove);
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(J.class);
+              assertThat(clazz, isPresent());
+              assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+              assertThat(inspector.clazz(I.class), not(isPresent()));
+            });
   }
 
   @Test
   public void testSingleInheritanceR8()
       throws CompilationFailedException, IOException, ExecutionException {
-    // TODO(b/143590191): Fix expectation when resolved.
-    testForR8(parameters.getBackend())
-        .addProgramClasses(I.class, J.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMethodRules(J.class, "void foo()")
-        .compile()
-        .inspect(AbstractInterfaceMethodsTest::inspectBaseInterfaceRemove);
-  }
-
-  private static void inspectBaseInterfaceRemove(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(J.class);
-    assertThat(clazz, isPresent());
-    assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
-    assertThat(inspector.clazz(I.class), not(isPresent()));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, J.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(J.class, "void foo()")
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
   }
 
   public interface I {
@@ -77,4 +80,19 @@
   }
 
   public interface J extends I {}
+
+  public static class A implements J {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((J) new A()).foo();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
index c6feb05..f75f7bd 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
@@ -11,11 +11,12 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
@@ -51,50 +52,92 @@
         .addKeepMethodRules(J.class, "void foo()")
         .addKeepRules("-dontwarn")
         .compile()
-        .inspect(DefaultInterfaceMethodsTest::inspectBaseInterfaceRemove);
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(J.class);
+              assertThat(clazz, isPresent());
+              assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+              assertThat(inspector.clazz(I.class), not(isPresent()));
+            });
   }
 
   @Test
-  public void testSingleInheritanceR8()
+  public void testSingleInheritanceR8BeforeNougat()
       throws CompilationFailedException, IOException, ExecutionException {
+    assumeTrue(parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, J.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(J.class, "void foo()")
+            .addOptionsModification(
+                internalOptions -> internalOptions.enableVerticalClassMerging = false)
+            .noMinification()
+            .compile();
+    // TODO(b/144269679): We should be able to compile and run this.
     testForR8(parameters.getBackend())
-        .addProgramClasses(I.class, J.class)
+        .addProgramClasses(ImplJ.class, Main.class)
+        .addClasspathClasses(I.class, J.class)
         .setMinApi(parameters.getApiLevel())
-        .addKeepMethodRules(J.class, "void foo()")
-        .compile()
-        .inspect(DefaultInterfaceMethodsTest::inspectBaseInterfaceRemove);
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(ImplJ.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, ImplJ.class.getTypeName(), "foo")
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "com.android.tools.r8.shaking.methods.interfaces.DefaultInterfaceMethodsTest$I$-CC"));
   }
 
-  private static void inspectBaseInterfaceRemove(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(J.class);
-    assertThat(clazz, isPresent());
-    assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
-    assertThat(inspector.clazz(I.class), not(isPresent()));
+  @Test
+  public void testSingleInheritanceR8OnNougatAndForward()
+      throws CompilationFailedException, IOException, ExecutionException {
+    assumeTrue(
+        parameters.isCfRuntime()
+            || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, J.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(J.class, "void foo()")
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(ImplJ.class, Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, ImplJ.class.getTypeName(), "foo")
+        .assertSuccessWithOutputLines("Hello World!");
   }
 
   @Test
   public void testKeepInterfaceMethodOnSubInterface()
       throws CompilationFailedException, IOException, ExecutionException {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, I.class, J.class, B.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(Main.class)
-        .addKeepClassAndMembersRules(B.class)
-        .addKeepMethodRules(J.class, "void foo()")
-        .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
-        .assertFailureWithErrorThatMatches(containsString("NoSuchMethodException"));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, J.class, ImplJ.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepClassAndMembersRules(ImplJ.class)
+            .addKeepMethodRules(J.class, "void foo()")
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, ImplJ.class.getTypeName(), "foo")
+        .assertSuccessWithOutputLines("Hello World!");
   }
 
   @Test
   public void testKeepInterfaceMethodOnImplementingType()
       throws CompilationFailedException, IOException, ExecutionException {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(Main.class, I.class, A.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepClassAndMembersRules(Main.class)
-        .addKeepMethodRules(A.class, "void <init>()", "void foo()")
-        .run(parameters.getRuntime(), Main.class, A.class.getTypeName(), "foo")
-        .assertFailureWithErrorThatMatches(containsString("NoSuchMethodException"));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, J.class, ImplJ.class, SubImplJ.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(SubImplJ.class, "void <init>()", "void foo()")
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, SubImplJ.class.getTypeName(), "foo")
+        .assertSuccessWithOutputLines("Hello World!");
   }
 
   public interface I {
@@ -106,9 +149,9 @@
 
   public interface J extends I {}
 
-  public static class A implements I {}
+  public static class ImplJ implements J {}
 
-  public static class B implements J {}
+  public static class SubImplJ extends ImplJ {}
 
   public static class Main {
 
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java
new file mode 100644
index 0000000..5171c66
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/ImplementingMethodInSubclassTest.java
@@ -0,0 +1,70 @@
+// 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 ImplementingMethodInSubclassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ImplementingMethodInSubclassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testKeepingIFooAndNotA()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, A.class, B.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(A.class, "void foo()")
+            .addKeepClassRules(B.class)
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World");
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  public abstract static class A implements I {}
+
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello World");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((A) new B()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/IndirectSuperInterfaceDefaultMethodTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/IndirectSuperInterfaceDefaultMethodTest.java
new file mode 100644
index 0000000..73398e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/IndirectSuperInterfaceDefaultMethodTest.java
@@ -0,0 +1,68 @@
+// 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 IndirectSuperInterfaceDefaultMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public IndirectSuperInterfaceDefaultMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testInvokeSpecial()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, J.class, K.class)
+            .addKeepClassRules(J.class)
+            .addKeepClassAndMembersRules(I.class)
+            .addKeepMethodRules(K.class, "void foo()")
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  public interface I {
+    default void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public interface J extends I {}
+
+  public interface K extends J {}
+
+  public static class A implements K {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((K) new A()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceAbstractTriangleTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceAbstractTriangleTest.java
new file mode 100644
index 0000000..cf15643
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceAbstractTriangleTest.java
@@ -0,0 +1,74 @@
+// 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InterfaceAbstractTriangleTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceAbstractTriangleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void shouldKeepA() throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, J.class, A.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(A.class, "void foo()", "void <init>()")
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(B.class, Main.class)
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  public interface I {
+
+    void foo();
+  }
+
+  public interface J {
+
+    void foo();
+  }
+
+  public abstract static class A implements I, J {}
+
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((A) new B()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceDefaultMethodKeptTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceDefaultMethodKeptTest.java
new file mode 100644
index 0000000..b7b517b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceDefaultMethodKeptTest.java
@@ -0,0 +1,135 @@
+// 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.shaking.methods.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InterfaceDefaultMethodKeptTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .build();
+  }
+
+  public InterfaceDefaultMethodKeptTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testKeepingAFooAndIFoo()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, A.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepClassAndMembersRules(I.class)
+            .addKeepMethodRules(A.class, "void <init>()", "void foo()")
+            .compile()
+            .inspect(
+                codeInspector -> {
+                  assertThat(codeInspector.clazz(A.class), isPresent());
+                  assertThat(
+                      codeInspector.clazz(A.class).uniqueMethodWithName("foo"), not(isPresent()));
+                });
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, A.class.getTypeName(), "foo")
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  @Test
+  public void testKeepingBFooAndIFoo()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, A.class, B.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepClassAndMembersRules(I.class)
+            .addKeepMethodRules(B.class, "void <init>()", "void foo()")
+            .compile()
+            .inspect(
+                codeInspector -> {
+                  assertThat(codeInspector.clazz(A.class), not(isPresent()));
+                  assertThat(codeInspector.clazz(B.class), isPresent());
+                  assertThat(
+                      codeInspector.clazz(B.class).uniqueMethodWithName("foo"), not(isPresent()));
+                });
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  @Test
+  public void testKeepingBFooAndAFoo()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(I.class, A.class, B.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(A.class, "void <init>()", "void foo()")
+            .addKeepMethodRules(B.class, "void <init>()", "void foo()")
+            .noMinification()
+            .compile()
+            .inspect(
+                codeInspector -> {
+                  assertThat(codeInspector.clazz(A.class), isPresent());
+                  assertThat(codeInspector.clazz(A.class).uniqueMethodWithName("foo"), isPresent());
+                  assertThat(codeInspector.clazz(B.class), isPresent());
+                  // TODO(b/144409021): We should be able to remove this.
+                  assertEquals(
+                      parameters.isDexRuntime(),
+                      codeInspector.clazz(B.class).uniqueMethodWithName("foo").isPresent());
+                });
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  interface I {
+
+    default void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class A implements I {}
+
+  public static class B extends A {}
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      Object o = Class.forName(args[0]).getDeclaredConstructor().newInstance();
+      o.getClass().getMethod(args[1]).invoke(o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodDefinedInLibraryTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodDefinedInLibraryTest.java
new file mode 100644
index 0000000..1254706
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodDefinedInLibraryTest.java
@@ -0,0 +1,74 @@
+// 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 InterfaceMethodDefinedInLibraryTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceMethodDefinedInLibraryTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMethodInLibrary()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult intermediateResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(A.class)
+            .addClasspathClasses(I.class)
+            .addKeepMethodRules(A.class, "void foo()", "void <init>()")
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addRunClasspathFiles(intermediateResult.writeToZip())
+        .addRunClasspathFiles(
+            parameters.isDexRuntime()
+                ? testForD8()
+                    .addProgramClasses(I.class)
+                    .setMinApi(parameters.getApiLevel())
+                    .compile()
+                    .writeToZip()
+                : ToolHelper.getClassPathForTests())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  public interface I {
+
+    default void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class A implements I {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java
new file mode 100644
index 0000000..cc6ec52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/InterfaceMethodKeepResolutionTest.java
@@ -0,0 +1,138 @@
+// 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+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 InterfaceMethodKeepResolutionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InterfaceMethodKeepResolutionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testKeepKShouldKeepK()
+      throws CompilationFailedException, IOException, ExecutionException {
+    runTest(
+        ImmutableList.of(I.class, J.class, K.class),
+        K.class,
+        ImmutableList.of(ImplK.class),
+        ImplK.class);
+  }
+
+  @Test
+  public void testKeepLShouldKeepIorL()
+      throws CompilationFailedException, IOException, ExecutionException {
+    runTest(
+        ImmutableList.of(I.class, J.class, L.class),
+        L.class,
+        ImmutableList.of(ImplL.class),
+        ImplL.class);
+  }
+
+  @Test
+  public void testKeepClassShouldKeepA()
+      throws CompilationFailedException, IOException, ExecutionException {
+    runTest(
+        ImmutableList.of(I.class, J.class, K.class, A.class),
+        A.class,
+        ImmutableList.of(B.class, Main.class),
+        Main.class);
+  }
+
+  private void runTest(
+      Collection<Class<?>> classPathClasses,
+      Class<?> libraryClassWithMethod,
+      Collection<Class<?>> programClasses,
+      Class<?> main)
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8FullTestBuilder libraryBuilder =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(classPathClasses)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(libraryClassWithMethod, "void foo()");
+    if (!libraryClassWithMethod.isInterface()) {
+      libraryBuilder.addKeepClassRules(libraryClassWithMethod);
+    }
+    testForRuntime(parameters)
+        .addProgramClasses(programClasses)
+        .addRunClasspathFiles(libraryBuilder.compile().writeToZip())
+        .run(parameters.getRuntime(), main)
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  public interface J extends I {
+    void foo();
+  }
+
+  public interface K extends J {}
+
+  public interface L extends I, J {}
+
+  public abstract static class A implements K {}
+
+  public static class ImplK implements K {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+
+    public static void main(String[] args) {
+      ((K) new ImplK()).foo();
+    }
+  }
+
+  public static class ImplL implements L {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+
+    public static void main(String[] args) {
+      ((L) new ImplL()).foo();
+    }
+  }
+
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      ((A) new B()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/LibraryInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/LibraryInterfaceMethodsTest.java
new file mode 100644
index 0000000..73bc53c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/LibraryInterfaceMethodsTest.java
@@ -0,0 +1,73 @@
+// 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.shaking.methods.interfaces;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 LibraryInterfaceMethodsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public LibraryInterfaceMethodsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testLibraryBridgeDesugaring()
+      throws CompilationFailedException, IOException, ExecutionException {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(BaseInterface.class, SubInterface.class)
+            .addLibraryFiles(runtimeJar(parameters))
+            .addKeepMethodRules(SubInterface.class, "int hashCode()")
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class, A.class.getTypeName(), "hashCode")
+        .assertSuccessWithOutputLines("Hello World!");
+  }
+
+  public interface BaseInterface {
+
+    int hashCode();
+  }
+
+  public interface SubInterface extends BaseInterface {}
+
+  public static class A implements SubInterface {
+
+    @Override
+    public int hashCode() {
+      System.out.println("Hello World!");
+      return 42;
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      Object o = Class.forName(args[0]).getDeclaredConstructor().newInstance();
+      o.getClass().getMethod(args[1]).invoke(o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleTargetTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleTargetTest.java
new file mode 100644
index 0000000..7e11e46
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleTargetTest.java
@@ -0,0 +1,117 @@
+// 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.shaking.methods.interfaces;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 MultipleTargetTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MultipleTargetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    assumeTrue(
+        parameters.isCfRuntime()
+            || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    testForRuntime(parameters)
+        .addProgramClasses(Top.class, Left.class, Right.class, Bottom.class, A.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(A.class.getName());
+  }
+
+  @Test
+  public void testSingleInheritanceR8BeforeNougat()
+      throws CompilationFailedException, IOException, ExecutionException {
+    assumeTrue(parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Top.class, Left.class, Right.class, Bottom.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMethodRules(Bottom.class, "java.lang.String name()")
+            .noMinification()
+            .compile();
+    // TODO(b/144269679): We should be able to compile and run this.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .addClasspathClasses(Top.class, Left.class, Right.class, Bottom.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(
+            containsString(
+                "com.android.tools.r8.shaking.methods.interfaces.MultipleTargetTest$Left$-CC"));
+  }
+
+  @Test
+  public void testKeepingBottomName()
+      throws CompilationFailedException, IOException, ExecutionException {
+    assumeTrue(
+        parameters.isCfRuntime()
+            || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Top.class, Left.class, Right.class, Bottom.class)
+            .addKeepMethodRules(Bottom.class, "java.lang.String name()")
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .addRunClasspathFiles(compileResult.writeToZip())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(A.class.getName());
+  }
+
+  interface Top {
+    default String name() {
+      return "unnamed";
+    }
+  }
+
+  interface Left extends Top {
+    default String name() {
+      return getClass().getName();
+    }
+  }
+
+  interface Right extends Top {
+    /* No override of default String name() */
+  }
+
+  interface Bottom extends Left, Right {}
+
+  public static class A implements Bottom {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A().name());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest.java
new file mode 100644
index 0000000..96803bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest.java
@@ -0,0 +1,155 @@
+// 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.shaking.methods.interfaces;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+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.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class StaticMethodsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimes()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .build();
+  }
+
+  public StaticMethodsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testForRuntime() throws ExecutionException, CompilationFailedException, IOException {
+    testForRuntime(parameters)
+        .addProgramClasses(I.class, J.class)
+        .addProgramClassFileData(StaticMethodsTest$MainForJDump.dump())
+        .run(parameters.getRuntime(), MainForJ.class)
+        .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+  }
+
+  public interface I {
+    static String foo() {
+      return foo("Hello ");
+    }
+
+    static String foo(String x) {
+      return x + "World!";
+    }
+  }
+
+  public interface J extends I {}
+
+  public static class ImplJ implements J {}
+
+  public static class MainForJ {
+
+    public static void main(String[] args) {
+      System.out.println(I.foo());
+    }
+  }
+
+  public static class StaticMethodsTest$MainForJDump implements Opcodes {
+
+    public static byte[] dump() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(
+          V1_8,
+          ACC_PUBLIC | ACC_SUPER,
+          "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$MainForJ",
+          null,
+          "java/lang/Object",
+          null);
+
+      classWriter.visitSource("StaticMethodsTest.java", null);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$MainForJ",
+          "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest",
+          "MainForJ",
+          ACC_PUBLIC | ACC_STATIC);
+
+      classWriter.visitInnerClass(
+          "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$I",
+          "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest",
+          "I",
+          ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
+
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(70, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable(
+            "this",
+            "Lcom/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$MainForJ;",
+            null,
+            label0,
+            label1,
+            0);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(73, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC,
+            "com/android/tools/r8/shaking/methods/interfaces/StaticMethodsTest$J",
+            "foo",
+            "()Ljava/lang/String;",
+            true);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(74, label1);
+        methodVisitor.visitInsn(RETURN);
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
+        methodVisitor.visitMaxs(2, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java
new file mode 100644
index 0000000..c8b8be2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/SubTypeOverridesInterfaceMethodTest.java
@@ -0,0 +1,80 @@
+// 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.shaking.methods.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This tests is showing the issue filed in b/143590191. The expectations for the test should
+ * reflect the decisions to keep the interface method or not in the super interface.
+ */
+@RunWith(Parameterized.class)
+public class SubTypeOverridesInterfaceMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SubTypeOverridesInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testKeepInterfaceMethodOnImplementingType()
+      throws CompilationFailedException, IOException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, I.class, A.class, B.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addKeepMethodRules(A.class, "void <init>()", "void foo()")
+        .addKeepClassRules(B.class)
+        .run(parameters.getRuntime(), Main.class, B.class.getTypeName(), "foo")
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(A.class);
+              assertThat(clazz, isPresent());
+              assertThat(clazz.uniqueMethodWithName("foo"), isPresent());
+            });
+  }
+
+  public interface I {
+    void foo();
+  }
+
+  public abstract static class A implements I {}
+
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) throws Exception {
+      Object o = Class.forName(args[0]).getDeclaredConstructor().newInstance();
+      o.getClass().getMethod(args[1]).invoke(o);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index a74db8e..b2313a7 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -38,7 +38,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ProxiesTest(TestParameters parameters) {
@@ -71,7 +71,7 @@
               o.inliningInstructionLimit = 4;
             })
         .noMinification()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(inspection)
         .run(parameters.getRuntime(), Main.class)
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 8157f47..428f9bb 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -176,8 +176,7 @@
   /** Continuation for transforming a method. Will continue with the super visitor if called. */
   @FunctionalInterface
   public interface MethodInsnTransformContinuation {
-    void visitMethodInsn(
-        int opcode, String owner, String name, String descriptor, boolean isInterface);
+    void apply(int opcode, String owner, String name, String descriptor, boolean isInterface);
   }
 
   public ClassFileTransformer transformMethodInsnInMethod(
diff --git a/src/test/java/com/android/tools/r8/utils/TestParametersTest.java b/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
index 2d26e5d..7d26b86 100644
--- a/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
+++ b/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
@@ -8,11 +8,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -71,4 +74,42 @@
           assertThat(apiLevels, hasItem(dexRuntime.getMinApiLevel()));
         });
   }
+
+  @Test
+  public void testJdk9Presence() {
+    assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
+        || TestParametersBuilder.getRuntimesProperty().contains("jdk9"));
+    assertTrue(TestParametersBuilder
+        .builder()
+        .withAllRuntimesAndApiLevels()
+        .build()
+        .stream()
+        .anyMatch(parameter -> parameter.getRuntime().equals(TestRuntime.getCheckedInJdk9())));
+  }
+
+  @Test
+  public void testDexDefaultPresence() {
+    assumeTrue(ToolHelper.isLinux());
+    assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
+        || TestParametersBuilder.getRuntimesProperty().contains("dex-default"));
+    assertTrue(TestParametersBuilder
+        .builder()
+        .withAllRuntimesAndApiLevels()
+        .build()
+        .stream()
+        .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-default")));
+  }
+
+  @Test
+  public void testDex444Presence() {
+    assumeTrue(ToolHelper.isLinux());
+    assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
+        || TestParametersBuilder.getRuntimesProperty().contains("dex-4.4.4"));
+    assertTrue(TestParametersBuilder
+        .builder()
+        .withAllRuntimesAndApiLevels()
+        .build()
+        .stream()
+        .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-4.4.4")));
+  }
 }
diff --git a/tools/archive.py b/tools/archive.py
index 62e0e27..d75242f 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -163,6 +163,10 @@
   if utils.cloud_storage_exists(destination) and not options.dry_run:
     raise Exception('Target archive directory %s already exists' % destination)
   with utils.TempDir() as temp:
+    # Create pom file for our maven repository that we build for testing.
+    default_pom_file = os.path.join(temp, 'r8.pom')
+    create_maven_release.write_default_r8_pom_file(default_pom_file, version)
+
     version_file = os.path.join(temp, 'r8-version.properties')
     with open(version_file,'w') as version_writer:
       version_writer.write('version.sha=' + GetGitHash() + '\n')
@@ -221,10 +225,14 @@
       if file == utils.R8_JAR:
         maven_dst = GetUploadDestination(utils.get_maven_path('r8', version),
                                          'r8-%s.jar' % version, is_master)
+        maven_pom_dst = GetUploadDestination(
+            utils.get_maven_path('r8', version),
+            'r8-%s.pom' % version, is_master)
         if options.dry_run:
           print('Dry run, not actually creating maven repo for R8')
         else:
           utils.upload_file_to_cloud_storage(tagged_jar, maven_dst)
+          utils.upload_file_to_cloud_storage(default_pom_file, maven_pom_dst)
           print('Maven repo root available at: %s' % GetMavenUrl(is_master))
 
       # Upload desugar_jdk_libs configuration to a maven compatible location.
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 6d7d8ab..72186a8 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -258,6 +258,9 @@
         group=group, artifact=artifact, version=version)
   return result
 
+def write_default_r8_pom_file(pom_file, version):
+  write_pom_file(R8_POMTEMPLATE, pom_file, version, generate_dependencies(), '')
+
 def write_pom_file(template, pom_file, version, dependencies='', library_licenses=''):
   version_pom = template.substitute(
       version=version, dependencies=dependencies, library_licenses=library_licenses)
diff --git a/tools/linux/README.art-versions b/tools/linux/README.art-versions
index 095872b..9cf0717 100644
--- a/tools/linux/README.art-versions
+++ b/tools/linux/README.art-versions
@@ -41,6 +41,28 @@
   repo init -u https://android.googlesource.com/platform/manifest -m aosp_master_manifest.xml
   <continue with repo sync as above>
 
+art-10.0.0 (Android Q)
+---------------------
+Build from branch android-10.0.0_r14.
+
+export BRANCH=android-10.0.0_r14
+mkdir ${BRANCH}
+cd ${BRANCH}
+repo init -u https://android.googlesource.com/platform/manifest -b ${BRANCH}
+repo sync -cq -j24
+source build/envsetup.sh
+lunch aosp_coral-userdebug
+m -j24
+m -j24 build-art
+m -j24 test-art-host
+
+Collected into tools/linux/art-10.0.0.
+
+  cd <r8 checkout>
+  scripts/update-host-art.sh \
+     --android-checkout /usr/local/ssd/android/${BRANCH} \
+     --art-dir art-10.0.0 \
+     --android-product coral
 
 art-9.0.0 (Android P)
 ---------------------
diff --git a/tools/linux/art-10.0.0.tar.gz.sha1 b/tools/linux/art-10.0.0.tar.gz.sha1
new file mode 100644
index 0000000..d64a0d7
--- /dev/null
+++ b/tools/linux/art-10.0.0.tar.gz.sha1
@@ -0,0 +1 @@
+04316aaf3b05ebfe17a35ea5f47d900ac3c32d69
\ No newline at end of file
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index add4b77..0941623 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -512,6 +512,7 @@
       extra_args.append('-Dcom.android.tools.r8.fieldBitAccessAnalysis=1')
       extra_args.append('-Dcom.android.tools.r8.generatedExtensionRegistryShrinking=1')
       extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteShrinking=1')
+      extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteBuilderShrinking=1')
       extra_args.append('-Dcom.android.tools.r8.stringSwitchConversion=1')
       extra_args.append('-Dcom.android.tools.r8.traverseOneOfAndRepeatedProtoFields=0')
 
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 85ef6f1..8b188f5 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -18,6 +18,7 @@
 import utils
 import zipfile
 from xml.dom import minidom
+from datetime import datetime
 
 import as_utils
 import update_prebuilds_in_android
@@ -681,7 +682,8 @@
 def BuildAppWithShrinker(
     app, repo, shrinker, checkout_dir, out_dir, temp_dir, options,
     keepRuleSynthesisForRecompilation=False):
-  print('Building {} with {}{}'.format(
+  print('[{}] Building {} with {}{}'.format(
+      datetime.now().strftime("%H:%M:%S"),
       app.name,
       shrinker,
       ' for recompilation' if keepRuleSynthesisForRecompilation else ''))
@@ -1293,7 +1295,13 @@
 
   with utils.TempDir() as temp_dir:
     if not (options.no_build or options.golem):
-      gradle.RunGradle(['r8', 'r8lib', '-Pno_internal'])
+      gradle.RunGradle(['r8', '-Pno_internal'])
+      build_r8lib = False
+      for shrinker in options.shrinker:
+        if IsMinifiedR8(shrinker):
+          build_r8lib = True
+      if build_r8lib:
+        gradle.RunGradle(['r8lib', '-Pno_internal'])
 
     if options.hash:
       # Download r8-<hash>.jar from
diff --git a/tools/test.py b/tools/test.py
index a51f89a..9925dea 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -22,6 +22,7 @@
 
 ALL_ART_VMS = [
     "default",
+    "10.0.0",
     "9.0.0",
     "8.1.0",
     "7.0.0",
@@ -155,6 +156,11 @@
 
 def Main():
   (options, args) = ParseOptions()
+  # See b/144966342
+  if options.dex_vm == '10.0.0':
+    print 'Running on 10.0.0 is temporarily disabled, see b/144966342'
+    return 0
+
   if utils.is_bot():
     gradle.RunGradle(['--no-daemon', 'clean'])
 
@@ -433,7 +439,6 @@
         test = href.replace('.html','').replace('#', '.').replace('.classMethod', '')
         failing.add(test)
   return list(failing)
-
 if __name__ == '__main__':
   return_code = Main()
   if return_code != 0: