Merge commit '93182839cf31f865030c47225a346f6a437f789f' into dev-release
diff --git a/.gitignore b/.gitignore
index e5a5af0..c5f7f13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,6 +124,12 @@
 third_party/openjdk/openjdk-9.0.4/windows.tar.gz
 third_party/openjdk/openjdk-rt-1.8
 third_party/openjdk/openjdk-rt-1.8.tar.gz
+third_party/opensource-apps/android-suite-calculator
+third_party/opensource-apps/android-suite-calculator.tar.gz
+third_party/opensource-apps/anexplorer
+third_party/opensource-apps/anexplorer.tar.gz
+third_party/opensource-apps/antennapod
+third_party/opensource-apps/antennapod.tar.gz
 third_party/opensource-apps/applymapping
 third_party/opensource-apps/applymapping.tar.gz
 third_party/opensource_apps
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index cd4e40c..c0222e8 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -364,6 +364,15 @@
       }
     }
     builders {
+      name: "linux-run-on-app-dump"
+      mixins: "linux"
+      mixins: "normal"
+      recipe {
+        properties_j: "test_options:[\"--bot\"]"
+        properties: "test_wrapper:tools/run_on_app_dump.py"
+      }
+    }
+    builders {
       name: "linux-run-on-as-app_release"
       mixins: "linux"
       mixins: "normal"
@@ -373,6 +382,15 @@
       }
     }
     builders {
+      name: "linux-run-on-app-dump_release"
+      mixins: "linux"
+      mixins: "normal"
+      recipe {
+        properties_j: "test_options:[\"--bot\"]"
+        properties: "test_wrapper:tools/run_on_app_dump.py"
+      }
+    }
+    builders {
       name: "linux-jctf"
       mixins: "linux"
       mixins: "jctf"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 29b0731..e76751b 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -86,6 +86,11 @@
     short_name: "apps-rec"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump"
+    category: "R8"
+    short_name: "apps-dump"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-jctf"
     category: "R8"
     short_name: "jctf"
@@ -166,6 +171,11 @@
     short_name: "apps"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-run-on-app-dump_release"
+    category: "R8 release"
+    short_name: "apps-dump"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/r8cf-linux-jctf_release"
     category: "R8 release"
     short_name: "cf-jctf"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index 040d32b..1e48f29 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -149,11 +149,21 @@
     repository: "https://r8.googlesource.com/r8"
   }
   builders {
+    name: "linux-run-on-app-dump"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
     name: "linux-run-on-as-app_release"
     bucket: "ci"
     repository: "https://r8.googlesource.com/r8"
   }
   builders {
+    name: "linux-run-on-app-dump_release"
+    bucket: "ci"
+    repository: "https://r8.googlesource.com/r8"
+  }
+  builders {
     name: "linux-jctf"
     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 3c01353..a0b3fa3 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -40,6 +40,7 @@
   triggers: "linux-android-10.0.0"
   triggers: "linux-run-on-as-app"
   triggers: "linux-run-on-as-app-recompilation"
+  triggers: "linux-run-on-app-dump"
   triggers: "linux-internal"
   triggers: "linux-jctf"
   triggers: "r8cf-linux-jctf"
@@ -77,6 +78,7 @@
   triggers: "linux-internal_release"
   triggers: "linux-jctf_release"
   triggers: "linux-run-on-as-app_release"
+  triggers: "linux-run-on-app-dump_release"
   triggers: "linux_release"
   triggers: "r8cf-linux-jctf_release"
   triggers: "windows_release"
@@ -454,6 +456,21 @@
   }
 }
 
+
+job {
+  id: "linux-run-on-app-dump"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 3
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-run-on-app-dump"
+  }
+}
+
 job {
   id: "linux-run-on-as-app_release"
   acl_sets: "default"
@@ -468,6 +485,19 @@
   }
 }
 
+job {
+  id: "linux-run-on-app-dump_release"
+  acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+    max_concurrent_invocations: 3
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-run-on-app-dump_release"
+  }
+}
 
 job {
   id: "linux-jctf"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 408e3a0..a38b29e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -60,6 +60,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
 import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.kotlin.KotlinMetadataUtils;
@@ -318,8 +319,10 @@
       InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
       BackportedMethodRewriter.registerAssumedLibraryTypes(options);
       if (options.enableEnumUnboxing) {
-        EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+        EnumUnboxingCfMethods.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
+      CfUtilityMethodsForCodeOptimizations.registerSynthesizedCodeReferences(
+          appView.dexItemFactory());
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 4661e68..7af534e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -66,7 +67,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerCheckCast(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 51f34fc..9d4ffa0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
 
@@ -97,8 +98,9 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
-    registry.registerConstClass(type);
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerConstClass(type, iterator);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 259a711..37d0ff4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfConstMethodHandle extends CfInstruction {
@@ -68,7 +69,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerMethodHandle(handle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 70417f6..b6a560f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
 
@@ -66,7 +67,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerProto(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index e9f11a8..b42194f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfDexItemBasedConstString extends CfInstruction {
@@ -89,7 +90,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     if (nameComputationInfo.needsToRegisterReference()) {
       assert item.isDexType();
       registry.registerTypeReference(item.asDexType());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 7c28eff..9b463fd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import java.util.Comparator;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -94,7 +95,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     switch (opcode) {
       case Opcodes.GETFIELD:
         registry.registerInstanceFieldRead(field);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index e9c06e0..2c81fce 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 
 public class CfInitClass extends CfInstruction {
@@ -77,7 +78,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInitClass(clazz);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index 1300458..899ef31 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -75,7 +76,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInstanceOf(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index b8874e8..3c36d33 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCompareHelper;
 import com.android.tools.r8.graph.ClasspathMethod;
@@ -21,9 +22,10 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 
-public abstract class CfInstruction {
+public abstract class CfInstruction implements CfOrDexInstruction {
 
   public abstract void write(
       AppView<?> appView,
@@ -72,15 +74,18 @@
     return printer.toString();
   }
 
-  public void registerUse(UseRegistry registry, ProgramMethod context) {
-    internalRegisterUse(registry, context);
+  public void registerUse(
+      UseRegistry registry, ProgramMethod context, ListIterator<CfInstruction> iterator) {
+    internalRegisterUse(registry, context, iterator);
   }
 
-  public void registerUseForDesugaring(UseRegistry registry, ClasspathMethod context) {
-    internalRegisterUse(registry, context);
+  public void registerUseForDesugaring(
+      UseRegistry registry, ClasspathMethod context, ListIterator<CfInstruction> iterator) {
+    internalRegisterUse(registry, context, iterator);
   }
 
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     // Intentionally empty.
   }
 
@@ -88,6 +93,16 @@
     return null;
   }
 
+  @Override
+  public CfInstruction asCfInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isCfInstruction() {
+    return true;
+  }
+
   public CfConstString asConstString() {
     return null;
   }
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 4765be5..4f5159b 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
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import java.util.Arrays;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -111,7 +112,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     Type invokeType = getInvokeType(context);
     switch (invokeType) {
       case DIRECT:
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index add492c..92384c8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.ListIterator;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
@@ -112,7 +113,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerCallSite(callSite);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 5f405d3..d52e429 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Comparator;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -75,7 +76,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerTypeReference(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index 60da1b2..a4be181 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -66,7 +67,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerNewInstance(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 30671bd..794227e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -112,7 +113,8 @@
   }
 
   @Override
-  void internalRegisterUse(UseRegistry registry, DexClassAndMethod context) {
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     if (!type.isPrimitiveArrayType()) {
       registry.registerTypeReference(type);
     }
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
new file mode 100644
index 0000000..e85c75c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.code;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+
+public interface CfOrDexInstruction {
+
+  CfInstruction asCfInstruction();
+
+  boolean isCfInstruction();
+}
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index 3c6d03e..5562a69 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -72,7 +72,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registry.registerConstClass(getType());
+    registry.registerConstClass(getType(), null);
   }
 
   public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 4ff81dc..52aa90d 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
+import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.DexCallSite;
@@ -21,7 +22,7 @@
 import java.nio.ShortBuffer;
 import java.util.function.BiPredicate;
 
-public abstract class Instruction implements Comparable<Instruction> {
+public abstract class Instruction implements CfOrDexInstruction, Comparable<Instruction> {
   public static final Instruction[] EMPTY_ARRAY = {};
 
   public final static int[] NO_TARGETS = null;
@@ -134,6 +135,16 @@
     this.offset = offset;
   }
 
+  @Override
+  public CfInstruction asCfInstruction() {
+    return null;
+  }
+
+  @Override
+  public boolean isCfInstruction() {
+    return false;
+  }
+
   public CheckCast asCheckCast() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 6ba885d..519bd27 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
@@ -76,8 +77,8 @@
     return result;
   }
 
-  public FeatureSplit getFeatureSplit(DexProgramClass clazz) {
-    return getFeatureSplit(clazz.getType());
+  public FeatureSplit getFeatureSplit(ProgramDefinition clazz) {
+    return getFeatureSplit(clazz.getContextType());
   }
 
   public FeatureSplit getFeatureSplit(DexType type) {
@@ -92,7 +93,7 @@
     return getFeatureSplit(clazz).isBase();
   }
 
-  public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
+  public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, ProgramDefinition context) {
     FeatureSplit split = getFeatureSplit(clazz);
     return split.isBase() || split == getFeatureSplit(context);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index ed19598..f872a56 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -27,8 +27,7 @@
       return OptionalBool.FALSE;
     }
     if (clazz.isProgramClass()
-        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
-            clazz.asProgramClass(), context.getContextClass())) {
+        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index bd6c452..7b1fe7c 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -178,7 +178,7 @@
     IRConverter converter = new IRConverter(appInfo, timing, printer);
     OneTimeMethodProcessor methodProcessor =
         OneTimeMethodProcessor.create(method, methodProcessingIdFactory);
-    methodProcessor.forEachWave(
+    methodProcessor.forEachWaveWithExtension(
         (ignore, methodProcesingId) ->
             converter.processMethod(
                 method,
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index dbefe85..d6ae59f 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -56,6 +56,7 @@
 import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.function.BiPredicate;
@@ -511,16 +512,20 @@
 
   @Override
   public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
-    for (CfInstruction instruction : instructions) {
-      instruction.registerUse(registry, method);
+    ListIterator<CfInstruction> iterator = instructions.listIterator();
+    while (iterator.hasNext()) {
+      CfInstruction instruction = iterator.next();
+      instruction.registerUse(registry, method, iterator);
     }
     tryCatchRanges.forEach(tryCatch -> tryCatch.internalRegisterUse(registry, method));
   }
 
   @Override
   public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
-    for (CfInstruction instruction : instructions) {
-      instruction.registerUseForDesugaring(registry, method);
+    ListIterator<CfInstruction> iterator = instructions.listIterator();
+    while (iterator.hasNext()) {
+      CfInstruction instruction = iterator.next();
+      instruction.registerUseForDesugaring(registry, method, iterator);
     }
     tryCatchRanges.forEach(tryCatch -> tryCatch.guards.forEach(registry::registerTypeReference));
   }
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 e827a25..c49b888 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.TraversalContinuation;
@@ -659,6 +660,16 @@
         appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
+  public boolean classInitializationMayHaveSideEffectsInContext(
+      AppView<AppInfoWithLiveness> appView, ProgramDefinition context) {
+    return classInitializationMayHaveSideEffects(
+        appView,
+        // Types that are a super type of the current context are guaranteed to be initialized
+        // already.
+        type -> appView.appInfo().isSubtype(context.getContextType(), type),
+        Sets.newIdentityHashSet());
+  }
+
   public boolean classInitializationMayHaveSideEffects(
       AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
     if (!seen.add(type)) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 13c4ac4..98128c4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
 import com.google.common.base.Strings;
 import com.google.common.collect.BiMap;
@@ -343,7 +344,9 @@
       createStaticallyKnownType(referenceFieldUpdaterDescriptor);
 
   public final DexType classType = createStaticallyKnownType(classDescriptor);
+  public final DexType packageType = createStaticallyKnownType(Package.class);
   public final DexType classLoaderType = createStaticallyKnownType(classLoaderDescriptor);
+  public final DexType constructorType = createStaticallyKnownType(constructorDescriptor);
   public final DexType fieldType = createStaticallyKnownType(fieldDescriptor);
   public final DexType methodType = createStaticallyKnownType(methodDescriptor);
   public final DexType autoCloseableType = createStaticallyKnownType(autoCloseableDescriptor);
@@ -1160,15 +1163,20 @@
     public final DexMethod desiredAssertionStatus;
     public final DexMethod forName;
     public final DexMethod forName3;
+    public final DexMethod getClassLoader =
+        createMethod(classType, createProto(classLoaderType), "getClassLoader");
     public final DexMethod getName;
     public final DexMethod getCanonicalName;
     public final DexMethod getSimpleName;
     public final DexMethod getTypeName;
+    public final DexMethod getConstructor;
     public final DexMethod getDeclaredConstructor;
     public final DexMethod getField;
     public final DexMethod getDeclaredField;
     public final DexMethod getMethod;
     public final DexMethod getDeclaredMethod;
+    public final DexMethod getPackage =
+        createMethod(classType, createProto(packageType), "getPackage");
     public final DexMethod newInstance;
     private final Set<DexMethod> getMembers;
     public final Set<DexMethod> getNames;
@@ -1195,6 +1203,8 @@
           classDescriptor, getSimpleNameName, stringDescriptor, DexString.EMPTY_ARRAY);
       getTypeName = createMethod(
           classDescriptor, getTypeNameName, stringDescriptor, DexString.EMPTY_ARRAY);
+      getConstructor =
+          createMethod(classType, createProto(constructorType, classArrayType), "getConstructor");
       getDeclaredConstructor =
           createMethod(
               classDescriptor,
@@ -1926,6 +1936,11 @@
     return createStaticallyKnownType(createString(descriptor));
   }
 
+  private DexType createStaticallyKnownType(Class<?> clazz) {
+    return createStaticallyKnownType(
+        createString(DescriptorUtils.javaTypeToDescriptor(clazz.getName())));
+  }
+
   private DexType createStaticallyKnownType(DexString descriptor) {
     DexType type = internalCreateType(descriptor);
     // Conservatively add all statically known types to "compiler synthesized types set".
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 8c9c20e..c29ebde 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -71,6 +71,11 @@
     return this;
   }
 
+  public static MethodAccessFlags createPublicStaticSynthetic() {
+    return fromSharedAccessFlags(
+        Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
+  }
+
   public static MethodAccessFlags fromSharedAccessFlags(int access, boolean isConstructor) {
     assert (access & SHARED_FLAGS) == access;
     assert CF_FLAGS == SHARED_FLAGS;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 82a133c..e96d26c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.origin.Origin;
+
 public interface ProgramDefinition {
 
   DexProgramClass getContextClass();
@@ -14,6 +16,8 @@
 
   DexDefinition getDefinition();
 
+  Origin getOrigin();
+
   default boolean isProgramClass() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 4a3327a..8f64090 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.CfOrDexInstruction;
+import java.util.ListIterator;
 
 public abstract class UseRegistry {
 
@@ -67,7 +69,8 @@
 
   public abstract void registerInstanceOf(DexType type);
 
-  public void registerConstClass(DexType type) {
+  public void registerConstClass(
+      DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
     registerTypeReference(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 667c1ff..04f9a85 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -156,7 +156,7 @@
     SortedProgramMethodSet wave =
         SortedProgramMethodSet.create(this::forEachFindLiteExtensionByNumberMethod);
     OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
-    methodProcessor.forEachWave(
+    methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingId) ->
             converter.processMethod(
                 method,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 6da2550..45f5083 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -80,7 +80,7 @@
     timing.begin("[Proto] Post optimize dynamic methods");
     SortedProgramMethodSet wave = SortedProgramMethodSet.create(this::forEachDynamicMethod);
     OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
-    methodProcessor.forEachWave(
+    methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingId) ->
             converter.processMethod(
                 method,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 2ef11c9..7dca039 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
@@ -12,6 +13,7 @@
 import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerUseRegistryFactory;
+import java.util.ListIterator;
 
 public class ProtoEnqueuerUseRegistry extends DefaultEnqueuerUseRegistry {
 
@@ -30,19 +32,20 @@
   }
 
   /**
-   * Unlike {@link DefaultEnqueuerUseRegistry#registerConstClass(DexType)}, this method does not
-   * trace any const-class instructions in every implementation of dynamicMethod().
+   * Unlike {@link DefaultEnqueuerUseRegistry#registerConstClass(DexType, ListIterator)}, this
+   * method does not trace any const-class instructions in every implementation of dynamicMethod().
    *
    * <p>The const-class instructions that remain after the proto schema has been optimized will be
    * traced manually by {@link ProtoEnqueuerExtension#tracePendingInstructionsInDynamicMethods}.
    */
   @Override
-  public void registerConstClass(DexType type) {
+  public void registerConstClass(
+      DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
     if (references.isDynamicMethod(getContextMethod())) {
       enqueuer.addDeadProtoTypeCandidate(type);
       return;
     }
-    super.registerConstClass(type);
+    super.registerConstClass(type, iterator);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index c4823bc..c9377f7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -407,12 +407,14 @@
           // Iterate in reverse order to ensure that POP instructions are inserted in correct order.
           for (int i = instruction.inValues().size() - 1; i >= 0; i--) {
             Value value = instruction.inValues().get(i);
-            if (value instanceof StackValue) {
-              if (value.definition.isLoad() && value.definition.getBlock() == this) {
+            if (value.isValueOnStack()) {
+              if (!value.isPhi()
+                  && (value.definition.isLoad() && value.definition.getBlock() == this)) {
                 assert hasLinearFlow(this, value.definition.getBlock());
                 value.definition.getBlock().removeInstruction(value.definition);
               } else {
-                Pop pop = new Pop((StackValue) value);
+                assert !(value instanceof StackValues);
+                Pop pop = new Pop(value);
                 pop.setBlock(this);
                 pop.setPosition(instruction.getPosition());
                 getInstructions().addLast(pop);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index e3fa34e..a3f8421 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -247,6 +249,19 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithConstClass(
+      AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+    if (current == null) {
+      throw new IllegalStateException();
+    }
+
+    TypeElement typeElement = TypeElement.classClassType(appView, definitelyNotNull());
+    Value value = code.createValue(typeElement, localInfo);
+    ConstClass constClass = new ConstClass(value, type);
+    replaceCurrentInstruction(constClass);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
     if (current == null) {
       throw new IllegalStateException();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 90e6097..8e7236c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -55,7 +55,7 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-public class IRCode {
+public class IRCode implements ValueFactory {
 
   private static final int MAX_MARKING_COLOR = 0x40000000;
 
@@ -1097,12 +1097,9 @@
     return thisValue;
   }
 
-  public Value createValue(TypeElement typeLattice, DebugLocalInfo local) {
-    return new Value(valueNumberGenerator.next(), typeLattice, local);
-  }
-
-  public Value createValue(TypeElement typeLattice) {
-    return createValue(typeLattice, null);
+  @Override
+  public Value createValue(TypeElement type, DebugLocalInfo local) {
+    return new Value(valueNumberGenerator.next(), type, local);
   }
 
   public ConstNumber createNumberConstant(long value, TypeElement type) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index bec911e..cb33b18 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -41,6 +42,12 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithConstClass(
+      AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+    instructionIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
     instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 61a1882..c878d9a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -36,6 +36,10 @@
     this.clazz = clazz;
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   public DexType getClassValue() {
     return clazz;
   }
@@ -175,4 +179,26 @@
   public String toString() {
     return super.toString() + "; " + clazz.toSourceString();
   }
+
+  public static class Builder extends BuilderBase<Builder, InitClass> {
+
+    private DexType type;
+
+    private Builder() {}
+
+    public Builder setType(DexType type) {
+      this.type = type;
+      return this;
+    }
+
+    @Override
+    public InitClass build() {
+      return amend(new InitClass(outValue, type));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 62c94b8..7fc0d77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1543,4 +1543,39 @@
       };
     }
   }
+
+  public abstract static class BuilderBase<B extends BuilderBase<B, I>, I extends Instruction> {
+
+    protected Value outValue;
+    protected Position position;
+
+    public abstract I build();
+
+    public abstract B self();
+
+    final I amend(I instruction) {
+      if (position != null) {
+        instruction.setPosition(position);
+      }
+      return instruction;
+    }
+
+    public B setOutValue(Value outValue) {
+      this.outValue = outValue;
+      return self();
+    }
+
+    public B setFreshOutValue(ValueFactory factory, TypeElement type) {
+      return setOutValue(factory.createValue(type));
+    }
+
+    public B setPosition(Position position) {
+      this.position = position;
+      return self();
+    }
+
+    public B setPosition(Instruction other) {
+      return setPosition(other.getPosition());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 2790e66..792018b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -19,6 +20,12 @@
 public interface InstructionListIterator
     extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
 
+  default void addBefore(Instruction instruction) {
+    previous();
+    add(instruction);
+    next();
+  }
+
   /** See {@link #replaceCurrentInstruction(Instruction, Set)}. */
   default void replaceCurrentInstruction(Instruction newInstruction) {
     replaceCurrentInstruction(newInstruction, null);
@@ -84,6 +91,9 @@
 
   Value insertConstStringInstruction(AppView<?> appView, IRCode code, DexString value);
 
+  void replaceCurrentInstructionWithConstClass(
+      AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
+
   void replaceCurrentInstructionWithConstInt(IRCode code, int value);
 
   void replaceCurrentInstructionWithStaticGet(
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 0e40a2a..31fd8f4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -156,6 +156,10 @@
     return arguments().get(index);
   }
 
+  public Value getFirstArgument() {
+    return getArgument(0);
+  }
+
   public int requiredArgumentRegisters() {
     int registers = 0;
     for (Value inValue : inValues) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index bf3a599..9bae360 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -27,7 +27,9 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
+import java.util.Collections;
 import java.util.List;
 
 public class InvokeStatic extends InvokeMethod {
@@ -44,6 +46,10 @@
     this.isInterface = isInterface;
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   @Override
   public boolean getInterfaceBit() {
     return isInterface;
@@ -234,4 +240,43 @@
             type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type),
             Sets.newIdentityHashSet());
   }
+
+  public static class Builder extends BuilderBase<Builder, InvokeStatic> {
+
+    private DexMethod method;
+    private List<Value> arguments = Collections.emptyList();
+
+    public Builder setArguments(List<Value> arguments) {
+      assert arguments != null;
+      this.arguments = arguments;
+      return this;
+    }
+
+    public Builder setSingleArgument(Value argument) {
+      return setArguments(ImmutableList.of(argument));
+    }
+
+    public Builder setMethod(DexMethod method) {
+      this.method = method;
+      return this;
+    }
+
+    public Builder setMethod(DexClassAndMethod method) {
+      return setMethod(method.getReference());
+    }
+
+    @Override
+    public InvokeStatic build() {
+      assert arguments != null;
+      assert method != null;
+      assert method.getArity() == arguments.size();
+      assert outValue == null || !method.getReturnType().isVoidType();
+      return amend(new InvokeStatic(method, outValue, arguments));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index d851eb9..0dd2895 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -54,6 +55,12 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithConstClass(
+      AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+    currentBlockIterator.replaceCurrentInstructionWithConstClass(appView, code, type, localInfo);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
     currentBlockIterator.replaceCurrentInstructionWithConstInt(code, value);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 9ec7256..a2f872e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -16,8 +16,9 @@
 
 public class Pop extends Instruction {
 
-  public Pop(StackValue src) {
+  public Pop(Value src) {
     super(null, src);
+    assert src.isValueOnStack();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java b/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java
new file mode 100644
index 0000000..ef1b16c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueFactory.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+
+public interface ValueFactory {
+
+  default Value createValue(TypeElement type) {
+    return createValue(type, null);
+  }
+
+  Value createValue(TypeElement type, DebugLocalInfo localInfo);
+}
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 6595192..7f29860 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
@@ -107,7 +107,7 @@
     }
 
     void push(Value value) {
-      assert value instanceof StackValue;
+      assert value.isValueOnStack();
       height += value.requiredRegisters();
       maxHeight = Math.max(maxHeight, height);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 6e47281..9b549d5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -57,7 +57,6 @@
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.StackValue;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -590,7 +589,7 @@
   private static boolean isNonMaterializingConstNumber(
       com.android.tools.r8.ir.code.Instruction instruction) {
     return instruction.isConstNumber()
-        && !(instruction.outValue() instanceof StackValue)
+        && !(instruction.outValue().isValueOnStack())
         && !(instruction.outValue().needsRegister());
   }
 
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 960dea0..b2c5888 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
@@ -111,7 +111,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -124,7 +123,7 @@
   private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
 
   public final AppView<?> appView;
-  public final Set<DexType> mainDexClasses;
+  public final MainDexTracingResult mainDexClasses;
 
   private final Timing timing;
   private final Outliner outliner;
@@ -196,7 +195,7 @@
     this.appView = appView;
     this.options = appView.options();
     this.printer = printer;
-    this.mainDexClasses = mainDexClasses.getClasses();
+    this.mainDexClasses = mainDexClasses;
     this.codeRewriter = new CodeRewriter(appView, this);
     this.constantCanonicalizer = new ConstantCanonicalizer(codeRewriter);
     this.classInitializerDefaultsOptimization =
@@ -728,7 +727,7 @@
         postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
     if (postMethodProcessor != null) {
       assert !options.debug;
-      postMethodProcessor.forEachWave(feedback, executorService);
+      postMethodProcessor.forEachWaveWithExtension(feedback, executorService);
       feedback.updateVisibleOptimizationInfo();
       assert graphLensForIR == appView.graphLens();
     }
@@ -1033,7 +1032,7 @@
       // Process the generated method, but don't apply any outlining.
       OneTimeMethodProcessor methodProcessor =
           OneTimeMethodProcessor.create(synthesizedMethod, appView);
-      methodProcessor.forEachWave(
+      methodProcessor.forEachWaveWithExtension(
           (method, methodProcessingId) ->
               processMethod(
                   method, delayedOptimizationFeedback, methodProcessor, methodProcessingId));
@@ -1044,7 +1043,7 @@
       SortedProgramMethodSet wave, ExecutorService executorService) throws ExecutionException {
     if (!wave.isEmpty()) {
       OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
-      methodProcessor.forEachWave(
+      methodProcessor.forEachWaveWithExtension(
           (method, methodProcessingId) ->
               processMethod(
                   method, delayedOptimizationFeedback, methodProcessor, methodProcessingId),
@@ -1305,7 +1304,8 @@
     if (appView.appInfo().hasLiveness()) {
       // Reflection optimization 1. getClass() / forName() -> const-class
       timing.begin("Rewrite to const class");
-      ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(appView.withLiveness(), code);
+      ReflectionOptimizer.rewriteGetClassOrForNameToConstClass(
+          appView.withLiveness(), code, mainDexClasses);
       timing.end();
     }
 
@@ -1342,7 +1342,8 @@
     assert code.verifyTypes(appView);
 
     timing.begin("Remove trivial type checks/casts");
-    codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+    codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
+        code, context, methodProcessor, methodProcessingId);
     timing.end();
 
     if (enumValueOptimizer != null) {
@@ -1388,7 +1389,8 @@
     timing.begin("Simplify control flow");
     if (codeRewriter.simplifyControlFlow(code)) {
       timing.begin("Remove trivial type checks/casts");
-      codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+      codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
+          code, context, methodProcessor, methodProcessingId);
       timing.end();
     }
     timing.end();
@@ -1466,6 +1468,7 @@
           code,
           feedback,
           methodProcessor,
+          methodProcessingId,
           inliner,
           Suppliers.memoize(
               () ->
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 8e1ee32..e1c9063 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -273,7 +273,7 @@
                         .setLocalInfo(invoke.outValue().getLocalInfo());
                   }
                   invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
-                  if (graphLens.lookupType(invoke.getReturnType()) != invoke.getReturnType()) {
+                  if (invoke.getOutType() != constantReturnMaterializingInstruction.getOutType()) {
                     affectedPhis.addAll(
                         constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
                   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
index 8e7cfea..143d6ff 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
@@ -25,10 +25,20 @@
     return id;
   }
 
+  public String getFullyQualifiedIdAndIncrement() {
+    String id = getFullyQualifiedId();
+    secondaryId++;
+    return id;
+  }
+
   public String getId() {
     if (secondaryId == 1) {
       return Integer.toString(primaryId);
     }
+    return getFullyQualifiedId();
+  }
+
+  public String getFullyQualifiedId() {
     return primaryId + "$" + secondaryId;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index b7ee7e1..86ff63f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -4,30 +4,49 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 
-public interface MethodProcessor {
+public abstract class MethodProcessor {
 
-  enum Phase {
+  public enum Phase {
     ONE_TIME,
     PRIMARY,
     POST
   }
 
-  Phase getPhase();
+  protected SortedProgramMethodSet wave;
+  protected SortedProgramMethodSet waveExtension = SortedProgramMethodSet.createConcurrent();
 
-  boolean shouldApplyCodeRewritings(ProgramMethod method);
+  public abstract Phase getPhase();
 
-  default boolean isPrimary() {
+  public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
+
+  public boolean isPrimary() {
     return getPhase() == Phase.PRIMARY;
   }
 
-  default boolean isPost() {
+  public boolean isPost() {
     return getPhase() == Phase.POST;
   }
 
-  default CallSiteInformation getCallSiteInformation() {
+  public CallSiteInformation getCallSiteInformation() {
     return CallSiteInformation.empty();
   }
 
-  boolean isProcessedConcurrently(ProgramMethod method);
+  public boolean isProcessedConcurrently(ProgramMethod method) {
+    return wave != null && wave.contains(method);
+  }
+
+  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+    waveExtension.add(method);
+  }
+
+  protected final void prepareForWaveExtensionProcessing() {
+    if (waveExtension.isEmpty()) {
+      wave = SortedProgramMethodSet.empty();
+    } else {
+      wave = waveExtension;
+      waveExtension = SortedProgramMethodSet.createConcurrent();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index 00bad3b..bb3f07c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -16,10 +16,9 @@
  * A {@link MethodProcessor} that doesn't persist; rather just processes the given methods one-time,
  * along with a default abstraction of concurrent processing.
  */
-public class OneTimeMethodProcessor implements MethodProcessor {
+public class OneTimeMethodProcessor extends MethodProcessor {
 
   private final MethodProcessingId.Factory methodProcessingIdFactory;
-  private final SortedProgramMethodSet wave;
 
   private OneTimeMethodProcessor(
       MethodProcessingId.Factory methodProcessingIdFactory, SortedProgramMethodSet wave) {
@@ -58,28 +57,29 @@
     return Phase.ONE_TIME;
   }
 
-  @Override
-  public boolean isProcessedConcurrently(ProgramMethod method) {
-    return wave != null && wave.contains(method);
-  }
-
-  public <E extends Exception> void forEachWave(
+  public <E extends Exception> void forEachWaveWithExtension(
       ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer) throws E {
-    ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
-    int i = 0;
-    for (ProgramMethod method : wave) {
-      consumer.accept(method, methodProcessingIds.get(method, i++));
+    while (!wave.isEmpty()) {
+      ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
+      int i = 0;
+      for (ProgramMethod method : wave) {
+        consumer.accept(method, methodProcessingIds.get(method, i++));
+      }
+      prepareForWaveExtensionProcessing();
     }
   }
 
-  public <E extends Exception> void forEachWave(
+  public <E extends Exception> void forEachWaveWithExtension(
       ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer,
       ExecutorService executorService)
       throws ExecutionException {
-    ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
-    ThreadUtils.processItems(
-        wave,
-        (method, index) -> consumer.accept(method, methodProcessingIds.get(method, index)),
-        executorService);
+    while (!wave.isEmpty()) {
+      ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
+      ThreadUtils.processItems(
+          wave,
+          (method, index) -> consumer.accept(method, methodProcessingIds.get(method, index)),
+          executorService);
+      prepareForWaveExtensionProcessing();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 5b1485d..48aaf3a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IROrdering;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
@@ -32,21 +31,23 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-public class PostMethodProcessor implements MethodProcessor {
+public class PostMethodProcessor extends MethodProcessor {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final Collection<CodeOptimization> defaultCodeOptimizations;
   private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
   private final Deque<SortedProgramMethodSet> waves;
-  private SortedProgramMethodSet wave;
   private final ProgramMethodSet processed = ProgramMethodSet.create();
 
   private PostMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
+      Collection<CodeOptimization> defaultCodeOptimizations,
       Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap,
       CallGraph callGraph) {
     this.appView = appView;
+    this.defaultCodeOptimizations = defaultCodeOptimizations;
     this.methodsMap = methodsMap;
-    this.waves = createWaves(appView, callGraph);
+    this.waves = createWaves(callGraph);
   }
 
   @Override
@@ -145,14 +146,13 @@
           new PartialCallGraphBuilder(
                   appView, methodsToReprocess.build(appView, appView.graphLens()))
               .build(executorService, timing);
-      return new PostMethodProcessor(appView, optimizationsMap, callGraph);
+      return new PostMethodProcessor(
+          appView, defaultCodeOptimizations, optimizationsMap, callGraph);
     }
   }
 
-  private Deque<SortedProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
-    IROrdering shuffle = appView.options().testing.irOrdering;
+  private Deque<SortedProgramMethodSet> createWaves(CallGraph callGraph) {
     Deque<SortedProgramMethodSet> waves = new ArrayDeque<>();
-
     int waveCount = 1;
     while (!callGraph.isEmpty()) {
       SortedProgramMethodSet wave = callGraph.extractRoots();
@@ -161,32 +161,37 @@
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
       }
     }
-
     return waves;
   }
 
   @Override
-  public boolean isProcessedConcurrently(ProgramMethod method) {
-    return wave != null && wave.contains(method);
+  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+    super.scheduleMethodForProcessingAfterCurrentWave(method);
+    methodsMap.put(method.getDefinition(), defaultCodeOptimizations);
   }
 
-  void forEachWave(OptimizationFeedback feedback, ExecutorService executorService)
+  void forEachWaveWithExtension(OptimizationFeedback feedback, ExecutorService executorService)
       throws ExecutionException {
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
-      assert wave.size() > 0;
-      ReservedMethodProcessingIds methodProcessingIds =
-          appView.methodProcessingIdFactory().reserveIds(wave);
-      ThreadUtils.processItems(
-          wave,
-          (method, index) -> {
-            Collection<CodeOptimization> codeOptimizations = methodsMap.get(method.getDefinition());
-            assert codeOptimizations != null && !codeOptimizations.isEmpty();
-            forEachMethod(
-                method, codeOptimizations, feedback, methodProcessingIds.get(method, index));
-          },
-          executorService);
-      processed.addAll(wave);
+      assert !wave.isEmpty();
+      assert waveExtension.isEmpty();
+      do {
+        ReservedMethodProcessingIds methodProcessingIds =
+            appView.methodProcessingIdFactory().reserveIds(wave);
+        ThreadUtils.processItems(
+            wave,
+            (method, index) -> {
+              Collection<CodeOptimization> codeOptimizations =
+                  methodsMap.get(method.getDefinition());
+              assert codeOptimizations != null && !codeOptimizations.isEmpty();
+              forEachMethod(
+                  method, codeOptimizations, feedback, methodProcessingIds.get(method, index));
+            },
+            executorService);
+        processed.addAll(wave);
+        prepareForWaveExtensionProcessing();
+      } while (!wave.isEmpty());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 9dfe8e3..dd4c0a0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -29,7 +29,7 @@
  * A {@link MethodProcessor} that processes methods in the whole program in a bottom-up manner,
  * i.e., from leaves to roots.
  */
-class PrimaryMethodProcessor implements MethodProcessor {
+class PrimaryMethodProcessor extends MethodProcessor {
 
   interface WaveStartAction {
 
@@ -40,7 +40,6 @@
   private final MethodProcessingId.Factory methodProcessingIdFactory;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
   private final Deque<SortedProgramMethodSet> waves;
-  private SortedProgramMethodSet wave;
 
   private PrimaryMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
@@ -105,11 +104,6 @@
     return waves;
   }
 
-  @Override
-  public boolean isProcessedConcurrently(ProgramMethod method) {
-    return wave != null && wave.contains(method);
-  }
-
   /**
    * Applies the given method to all leaf nodes of the graph.
    *
@@ -127,20 +121,25 @@
         timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
-      assert wave.size() > 0;
-      waveStartAction.notifyWaveStart(wave);
-      ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
-      Collection<Timing> timings =
-          ThreadUtils.processItemsWithResults(
-              wave,
-              (method, index) -> {
-                Timing time = consumer.apply(method, methodProcessingIds.get(method, index));
-                time.end();
-                return time;
-              },
-              executorService);
-      merger.add(timings);
-      waveDone.accept(wave);
+      assert !wave.isEmpty();
+      assert waveExtension.isEmpty();
+      do {
+        waveStartAction.notifyWaveStart(wave);
+        ReservedMethodProcessingIds methodProcessingIds =
+            methodProcessingIdFactory.reserveIds(wave);
+        Collection<Timing> timings =
+            ThreadUtils.processItemsWithResults(
+                wave,
+                (method, index) -> {
+                  Timing time = consumer.apply(method, methodProcessingIds.get(method, index));
+                  time.end();
+                  return time;
+                },
+                executorService);
+        merger.add(timings);
+        waveDone.accept(wave);
+        prepareForWaveExtensionProcessing();
+      } while (!wave.isEmpty());
     }
     merger.end();
   }
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 61b13cb..d765d76 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
@@ -82,6 +82,9 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -1302,7 +1305,11 @@
     REMOVED_CAST_DO_NARROW
   }
 
-  public void removeTrivialCheckCastAndInstanceOfInstructions(IRCode code) {
+  public void removeTrivialCheckCastAndInstanceOfInstructions(
+      IRCode code,
+      ProgramMethod context,
+      MethodProcessor methodProcessor,
+      MethodProcessingId methodProcessingId) {
     if (!appView.enableWholeProgramOptimizations()) {
       return;
     }
@@ -1346,7 +1353,14 @@
         boolean hasPhiUsers = current.outValue().hasPhiUsers();
         RemoveCheckCastInstructionIfTrivialResult removeResult =
             removeCheckCastInstructionIfTrivial(
-                appViewWithLiveness, current.asCheckCast(), it, code, affectedValues);
+                appViewWithLiveness,
+                current.asCheckCast(),
+                it,
+                code,
+                context,
+                affectedValues,
+                methodProcessor,
+                methodProcessingId);
         if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) {
           assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
           needToRemoveTrivialPhis |= hasPhiUsers;
@@ -1382,7 +1396,10 @@
       CheckCast checkCast,
       InstructionListIterator it,
       IRCode code,
-      Set<Value> affectedValues) {
+      ProgramMethod context,
+      Set<Value> affectedValues,
+      MethodProcessor methodProcessor,
+      MethodProcessingId methodProcessingId) {
     Value inValue = checkCast.object();
     Value outValue = checkCast.outValue();
     DexType castType = checkCast.getType();
@@ -1438,6 +1455,39 @@
       return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
     }
 
+    // If values of cast type are guaranteed to be null, then the out-value must be null if the cast
+    // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced
+    // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast
+    // type.
+    if (castType.isClassType()
+        && castType.isAlwaysNull(appViewWithLiveness)
+        && !outValue.hasDebugUsers()) {
+      // Replace all usages of the out-value by null.
+      it.previous();
+      Value nullValue = it.insertConstNullInstruction(code, options);
+      it.next();
+      checkCast.outValue().replaceUsers(nullValue);
+      affectedValues.addAll(nullValue.affectedValues());
+
+      // Replace the check-cast instruction by throwClassCastExceptionIfNotNull().
+      UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
+          UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
+              appView, context, methodProcessingId);
+      // TODO(b/172194277): Allow synthetics when generating CF.
+      if (throwClassCastExceptionIfNotNullMethod != null) {
+        throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
+        InvokeStatic replacement =
+            InvokeStatic.builder()
+                .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
+                .setSingleArgument(checkCast.object())
+                .setPosition(checkCast)
+                .build();
+        it.replaceCurrentInstruction(replacement);
+        assert replacement.lookupSingleTarget(appView, context) != null;
+        return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
+      }
+    }
+
     // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
     // A < B < C
     // c = ...        // Even though we know c is of type A,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 4e89840..cc5e7ed 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -4,8 +4,9 @@
 package com.android.tools.r8.ir.optimize;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -13,36 +14,35 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.Sets;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 public class ReflectionOptimizer {
 
   // Rewrite getClass() to const-class if the type of the given instance is effectively final.
   // Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
   public static void rewriteGetClassOrForNameToConstClass(
-      AppView<AppInfoWithLiveness> appView, IRCode code) {
+      AppView<AppInfoWithLiveness> appView, IRCode code, MainDexTracingResult mainDexClasses) {
     if (!appView.appInfo().canUseConstClassInstructions(appView.options())) {
       return;
     }
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     ProgramMethod context = code.context();
-    ClassInitializationAnalysis classInitializationAnalysis =
-        new ClassInitializationAnalysis(appView, code);
     for (BasicBlock block : code.blocks) {
       // Conservatively bail out if the containing block has catch handlers.
       // TODO(b/118509730): unless join of all catch types is ClassNotFoundException ?
@@ -51,32 +51,28 @@
       }
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
-        Instruction current = it.next();
-        if (!current.hasOutValue() || !current.outValue().isUsed()) {
+        InvokeMethod invoke = it.nextUntil(x -> x.isInvokeStatic() || x.isInvokeVirtual());
+        if (invoke == null) {
           continue;
         }
-        DexType type = null;
-        if (current.isInvokeVirtual()) {
-          type = getTypeForGetClass(appView, context, current.asInvokeVirtual());
-        } else if (current.isInvokeStatic()) {
-          type = getTypeForClassForName(
-              appView, classInitializationAnalysis, context, current.asInvokeStatic());
-        }
-        if (type != null) {
-          affectedValues.addAll(current.outValue().affectedValues());
-          TypeElement typeLattice = TypeElement.classClassType(appView, definitelyNotNull());
-          Value value = code.createValue(typeLattice, current.getLocalInfo());
-          ConstClass constClass = new ConstClass(value, type);
-          it.replaceCurrentInstruction(constClass);
-          if (appView.options().isGeneratingClassFiles()) {
-            code.method()
-                .upgradeClassFileVersion(
-                    appView.options().requiredCfVersionForConstClassInstructions());
-          }
+
+        if (invoke.isInvokeStatic()) {
+          applyTypeForClassForNameTo(
+              appView,
+              context,
+              invoke.asInvokeStatic(),
+              rewriteSingleGetClassOrForNameToConstClass(
+                  appView, code, it, invoke, affectedValues, mainDexClasses));
+        } else {
+          applyTypeForGetClassTo(
+              appView,
+              context,
+              invoke.asInvokeVirtual(),
+              rewriteSingleGetClassOrForNameToConstClass(
+                  appView, code, it, invoke, affectedValues, mainDexClasses));
         }
       }
     }
-    classInitializationAnalysis.finish();
     // Newly introduced const-class is not null, and thus propagate that information.
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -84,24 +80,79 @@
     assert code.isConsistentSSA();
   }
 
-  private static DexType getTypeForGetClass(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod context, InvokeVirtual invoke) {
+  private static BiConsumer<DexType, DexClass> rewriteSingleGetClassOrForNameToConstClass(
+      AppView<AppInfoWithLiveness> appView,
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      Set<Value> affectedValues,
+      MainDexTracingResult mainDexClasses) {
+    return (type, baseClass) -> {
+      if (invoke.getInvokedMethod().match(appView.dexItemFactory().classMethods.forName)) {
+        // Bail-out if the optimization could increase the size of the main dex.
+        if (baseClass.isProgramClass()
+            && !mainDexClasses.canReferenceItemFromContextWithoutIncreasingMainDexSize(
+                baseClass.asProgramClass(), code.context())) {
+          return;
+        }
+
+        // We need to initialize the type if it may have observable side effects.
+        if (type.isClassType()
+            && baseClass.classInitializationMayHaveSideEffectsInContext(appView, code.context())) {
+          if (!baseClass.isProgramClass() || !appView.canUseInitClass()) {
+            // No way to trigger the class initialization of the given class without
+            // Class.forName(), so skip.
+            return;
+          }
+
+          instructionIterator.addBefore(
+              InitClass.builder()
+                  .setFreshOutValue(code, TypeElement.getInt())
+                  .setType(type)
+                  .setPosition(invoke)
+                  .build());
+        }
+      }
+
+      // If there are no users of the const-class then simply remove the instruction.
+      if (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers()) {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+        return;
+      }
+
+      // Otherwise insert a const-class instruction.
+      affectedValues.addAll(invoke.outValue().affectedValues());
+      instructionIterator.replaceCurrentInstructionWithConstClass(
+          appView, code, type, invoke.getLocalInfo());
+      if (appView.options().isGeneratingClassFiles()) {
+        code.method()
+            .upgradeClassFileVersion(
+                appView.options().requiredCfVersionForConstClassInstructions());
+      }
+    };
+  }
+
+  private static void applyTypeForGetClassTo(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      InvokeVirtual invoke,
+      BiConsumer<DexType, ? super DexClass> consumer) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexMethod invokedMethod = invoke.getInvokedMethod();
     // Class<?> Object#getClass() is final and cannot be overridden.
     if (invokedMethod != dexItemFactory.objectMembers.getClass) {
-      return null;
+      return;
     }
     Value in = invoke.getReceiver();
     if (in.hasLocalInfo()) {
-      return null;
+      return;
     }
     TypeElement inType = in.getType();
     // Check the receiver is either class type or array type. Also make sure it is not
     // nullable.
     if (!(inType.isClassType() || inType.isArrayType())
         || inType.isNullable()) {
-      return null;
+      return;
     }
     DexType type =
         inType.isClassType()
@@ -110,49 +161,49 @@
     DexType baseType = type.toBaseType(dexItemFactory);
     // Make sure base type is a class type.
     if (!baseType.isClassType()) {
-      return null;
+      return;
     }
     // Only consider program class, e.g., platform can introduce subtypes in different
     // versions.
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(baseType));
     if (clazz == null) {
-      return null;
+      return;
     }
     // Only consider effectively final class. Exception: new Base().getClass().
     if (!clazz.isEffectivelyFinal(appView)
         && (in.isPhi() || !in.definition.isCreatingInstanceOrArray())) {
-      return null;
+      return;
     }
     // Make sure the target (base) type is visible.
     ConstraintWithTarget constraints =
         ConstraintWithTarget.classIsVisible(context, baseType, appView);
     if (constraints == ConstraintWithTarget.NEVER) {
-      return null;
+      return;
     }
-    return type;
+
+    consumer.accept(type, clazz);
   }
 
-  private static DexType getTypeForClassForName(
+  private static void applyTypeForClassForNameTo(
       AppView<AppInfoWithLiveness> appView,
-      ClassInitializationAnalysis classInitializationAnalysis,
       ProgramMethod context,
-      InvokeStatic invoke) {
+      InvokeStatic invoke,
+      BiConsumer<DexType, ? super DexClass> consumer) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexMethod invokedMethod = invoke.getInvokedMethod();
     // Class<?> Class#forName(String) is final and cannot be overridden.
     if (invokedMethod != dexItemFactory.classMethods.forName) {
-      return null;
+      return;
     }
-    assert invoke.inValues().size() == 1;
-    Value in = invoke.inValues().get(0).getAliasedValue();
+    assert invoke.arguments().size() == 1;
+    Value in = invoke.getArgument(0).getAliasedValue();
     // Only consider const-string input without locals.
     if (in.hasLocalInfo() || in.isPhi()) {
-      return null;
+      return;
     }
     // Also, check if the result of forName() is updatable via locals.
-    Value out = invoke.outValue();
-    if (out != null && out.hasLocalInfo()) {
-      return null;
+    if (invoke.hasOutValue() && invoke.outValue().hasLocalInfo()) {
+      return;
     }
     DexType type = null;
     if (in.definition.isDexItemBasedConstString()) {
@@ -172,46 +223,39 @@
       }
       if (descriptor == null
           || descriptor.indexOf(DescriptorUtils.JAVA_PACKAGE_SEPARATOR) > 0) {
-        return null;
+        return;
       }
       type = dexItemFactory.createType(descriptor);
       // Check if the given name refers to a reference type.
       if (!type.isReferenceType()) {
-        return null;
+        return;
       }
     } else {
       // Bail out for non-deterministic input to Class<?>#forName(name).
-      return null;
+      return;
     }
     if (type == null) {
-      return null;
+      return;
     }
     // Make sure the (base) type is resolvable.
     DexType baseType = type.toBaseType(dexItemFactory);
-    DexClass baseClazz = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
-    if (baseClazz == null || !baseClazz.isResolvable(appView)) {
-      return null;
+    DexClass baseClass = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
+    if (baseClass == null || !baseClass.isResolvable(appView)) {
+      return;
     }
 
-    // Don't allow the instantiated class to be in a feature, if it is, we can get a
-    // NoClassDefFoundError from dalvik/art.
-    if (baseClazz.isProgramClass()
-        && appView.appInfo().getClassToFeatureSplitMap().isInFeature(baseClazz.asProgramClass())) {
-      return null;
-    }
     // Make sure the (base) type is visible.
-    ConstraintWithTarget constraints =
-        ConstraintWithTarget.classIsVisible(context, baseType, appView);
-    if (constraints == ConstraintWithTarget.NEVER) {
-      return null;
+    ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
+    if (AccessControl.isClassAccessible(baseClass, context, classToFeatureSplitMap)
+        .isPossiblyFalse()) {
+      return;
     }
-    // Make sure the type is already initialized.
-    // Note that, if the given name refers to an array type, the corresponding Class<?> won't
-    // be initialized. So, it's okay to rewrite the instruction.
-    if (type.isClassType()
-        && !classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(type, invoke)) {
-      return null;
-    }
-    return type;
+
+    // If the type is guaranteed to be visible, it must be in the same feature as the current method
+    // or in the base.
+    assert !baseClass.isProgramClass()
+        || classToFeatureSplitMap.isInBaseOrSameFeatureAs(baseClass.asProgramClass(), context);
+
+    consumer.accept(type, baseClass);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
new file mode 100644
index 0000000..247e2e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class UtilityMethodsForCodeOptimizations {
+
+  public static UtilityMethodForCodeOptimizations synthesizeThrowClassCastExceptionIfNotNullMethod(
+      AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+    InternalOptions options = appView.options();
+    if (options.isGeneratingClassFiles()) {
+      // TODO(b/172194277): Allow synthetics when generating CF.
+      return null;
+    }
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    ProgramMethod syntheticMethod =
+        syntheticItems.createMethod(
+            context,
+            dexItemFactory,
+            builder ->
+                builder
+                    .setProto(proto)
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(
+                        method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options)),
+            methodProcessingId);
+    return new UtilityMethodForCodeOptimizations(syntheticMethod);
+  }
+
+  private static CfCode getThrowClassCastExceptionIfNotNullCodeTemplate(
+      DexMethod method, InternalOptions options) {
+    return CfUtilityMethodsForCodeOptimizations
+        .CfUtilityMethodsForCodeOptimizationsTemplates_throwClassCastExceptionIfNotNull(
+            options, method);
+  }
+
+  public static class UtilityMethodForCodeOptimizations {
+
+    private final ProgramMethod method;
+    private boolean optimized;
+
+    private UtilityMethodForCodeOptimizations(ProgramMethod method) {
+      this.method = method;
+    }
+
+    public ProgramMethod getMethod() {
+      assert optimized;
+      return method;
+    }
+
+    public void optimize(MethodProcessor methodProcessor) {
+      methodProcessor.scheduleMethodForProcessingAfterCurrentWave(method);
+      optimized = true;
+    }
+  }
+}
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 6f945bd..dab89c6 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
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionOrPhi;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -132,6 +133,7 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
+      MethodProcessingId methodProcessingId,
       Inliner inliner,
       Supplier<InliningOracle> defaultOracle) {
 
@@ -250,7 +252,8 @@
       // If a method was inlined we may be able to remove check-cast instructions because we may
       // have more information about the types of the arguments at the call site. This is
       // particularly important for bridge methods.
-      codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
+      codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
+          code, method, methodProcessor, methodProcessingId);
       // If a method was inlined we may be able to prune additional branches.
       codeRewriter.simplifyControlFlow(code);
       // If a method was inlined we may see more trivial computation/conversion of String.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 334e4fa..b46805b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -158,7 +158,7 @@
         }
         switch (instruction.opcode()) {
           case Opcodes.CONST_CLASS:
-            analyzeConstClass(instruction.asConstClass(), eligibleEnums);
+            analyzeConstClass(instruction.asConstClass(), eligibleEnums, code.context());
             break;
           case Opcodes.CHECK_CAST:
             analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
@@ -244,7 +244,8 @@
         TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView));
   }
 
-  private void analyzeConstClass(ConstClass constClass, Set<DexType> eligibleEnums) {
+  private void analyzeConstClass(
+      ConstClass constClass, Set<DexType> eligibleEnums, ProgramMethod context) {
     // We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
     // We however allow unboxing if the ConstClass is used only:
     // - as an argument to Enum#valueOf, to allow unboxing of:
@@ -269,15 +270,18 @@
           && isUnboxableNameMethod(user.asInvokeVirtual().getInvokedMethod())) {
         continue;
       }
-      if (!(user.isInvokeStatic()
-          && user.asInvokeStatic().getInvokedMethod() == factory.enumMembers.valueOf)) {
-        markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
-        return;
+      if (user.isInvokeStatic()) {
+        DexClassAndMethod singleTarget = user.asInvokeStatic().lookupSingleTarget(appView, context);
+        if (singleTarget != null && singleTarget.getReference() == factory.enumMembers.valueOf) {
+          // The name data is required for the correct mapping from the enum name to the ordinal in
+          // the valueOf utility method.
+          addRequiredNameData(enumType);
+          continue;
+        }
       }
+      markEnumAsUnboxable(Reason.CONST_CLASS, enumClass);
+      return;
     }
-    // The name data is required for the correct mapping from the enum name to the ordinal in the
-    // valueOf utility method.
-    addRequiredNameData(enumType);
     eligibleEnums.add(enumType);
   }
 
@@ -372,6 +376,7 @@
     NestedGraphLens enumUnboxingLens =
         new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
             .fixupTypeReferences();
+    enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
     appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
     GraphLens previousLens = appView.graphLens();
     appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 33cc6c4..27c125a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -26,6 +27,7 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -77,6 +79,7 @@
   private final EnumValueInfoMapCollection enumsToUnbox;
   private final EnumInstanceFieldDataMap unboxedEnumsInstanceFieldData;
   private final UnboxedEnumMemberRelocator relocator;
+  private NestedGraphLens enumUnboxingLens;
 
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
   private final Map<DexField, DexEncodedField> utilityFields = new ConcurrentHashMap<>();
@@ -140,6 +143,10 @@
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
   }
 
+  public void setEnumUnboxingLens(NestedGraphLens enumUnboxingLens) {
+    this.enumUnboxingLens = enumUnboxingLens;
+  }
+
   public EnumValueInfoMapCollection getEnumsToUnbox() {
     return enumsToUnbox;
   }
@@ -151,6 +158,7 @@
       return Sets.newIdentityHashSet();
     }
     assert code.isConsistentSSABeforeTypesAreCorrect();
+    ProgramMethod context = code.context();
     Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blocks = code.listIterator();
@@ -162,18 +170,23 @@
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
         // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
-        // counterpart.
+        // counterpart. The rewriting (== or match) is based on the following:
+        // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
+        // - equals, hashCode are final and implemented in java.lang.Enum and java.lang.Object,
+        // - getClass is final and implemented only in java.lang.Object,
+        // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
+        //   also in the unboxed enum class.
         if (instruction.isInvokeMethodWithReceiver()) {
           InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
-          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
           DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
           if (enumType != null) {
+            DexMethod invokedMethod = invokeMethod.getInvokedMethod();
             if (invokedMethod == factory.enumMembers.ordinalMethod
-                || invokedMethod == factory.enumMembers.hashCode) {
+                || invokedMethod.match(factory.enumMembers.hashCode)) {
               replaceEnumInvoke(
                   iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
               continue;
-            } else if (invokedMethod == factory.enumMembers.equals) {
+            } else if (invokedMethod.match(factory.enumMembers.equals)) {
               replaceEnumInvoke(
                   iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
               continue;
@@ -181,14 +194,18 @@
               replaceEnumInvoke(
                   iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
               continue;
-            } else if (invokedMethod == factory.enumMembers.nameMethod
-                || invokedMethod == factory.enumMembers.toString) {
-              DexMethod toStringMethod =
-                  computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
-              iterator.replaceCurrentInstruction(
-                  new InvokeStatic(
-                      toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
+            } else if (invokedMethod == factory.enumMembers.nameMethod) {
+              rewriteNameMethod(iterator, invokeMethod, enumType);
               continue;
+            } else if (invokedMethod.match(factory.enumMembers.toString)) {
+              DexMethod lookupMethod = enumUnboxingLens.lookupMethod(invokedMethod);
+              // If the lookupMethod is different, then a toString method was on the enumType
+              // class, which was moved, and the lens code rewriter will rewrite the invoke to
+              // that method.
+              if (invokeMethod.isInvokeSuper() || lookupMethod == invokedMethod) {
+                rewriteNameMethod(iterator, invokeMethod, enumType);
+                continue;
+              }
             } else if (invokedMethod == factory.objectMembers.getClass) {
               assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
               replaceEnumInvoke(
@@ -197,11 +214,15 @@
           }
         } else if (instruction.isInvokeStatic()) {
           InvokeStatic invokeStatic = instruction.asInvokeStatic();
-          DexMethod invokedMethod = invokeStatic.getInvokedMethod();
+          DexClassAndMethod singleTarget = invokeStatic.lookupSingleTarget(appView, context);
+          if (singleTarget == null) {
+            continue;
+          }
+          DexMethod invokedMethod = singleTarget.getReference();
           if (invokedMethod == factory.enumMembers.valueOf
-              && invokeStatic.inValues().get(0).isConstClass()) {
+              && invokeStatic.getArgument(0).isConstClass()) {
             DexType enumType =
-                invokeStatic.inValues().get(0).getConstInstruction().asConstClass().getValue();
+                invokeStatic.getArgument(0).getConstInstruction().asConstClass().getValue();
             if (enumsToUnbox.containsEnum(enumType)) {
               DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
               Value outValue = invokeStatic.outValue();
@@ -322,6 +343,9 @@
           ArrayAccess arrayAccess = instruction.asArrayAccess();
           DexType enumType = getEnumTypeOrNull(arrayAccess);
           if (enumType != null) {
+            if (arrayAccess.hasOutValue()) {
+              affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
+            }
             instruction = arrayAccess.withMemberType(MemberType.INT);
             iterator.replaceCurrentInstruction(instruction);
             convertedEnums.put(instruction, enumType);
@@ -334,6 +358,14 @@
     return affectedPhis;
   }
 
+  private void rewriteNameMethod(
+      InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
+    DexMethod toStringMethod =
+        computeInstanceFieldUtilityMethod(enumType, factory.enumMembers.nameField);
+    iterator.replaceCurrentInstruction(
+        new InvokeStatic(toStringMethod, invokeMethod.outValue(), invokeMethod.arguments()));
+  }
+
   private Value fixNullsInBlockPhis(IRCode code, BasicBlock block, Value zeroConstValue) {
     for (Phi phi : block.getPhis()) {
       if (getEnumTypeOrNull(phi.getType()) != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 280dc9b..1dd8e0c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -113,7 +113,8 @@
     encodedMethod.accessFlags.promoteToStatic();
     encodedMethod.clearAnnotations();
     encodedMethod.clearParameterAnnotations();
-    return encodedMethod.toTypeSubstitutedMethod(newMethod);
+    return encodedMethod.toTypeSubstitutedMethod(
+        newMethod, builder -> builder.setCompilationState(encodedMethod.getCompilationState()));
   }
 
   private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
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 cb6a6fa..be88541 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
@@ -99,11 +99,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.WorkList;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -841,15 +839,14 @@
           if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
             return InstructionEffect.CONDITIONAL_EFFECT;
           }
-          if (isKotlinNullCheck(instr, value, appView)) {
-            DexMethod invokedMethod = instr.asInvokeStatic().getInvokedMethod();
-            // Kotlin specific way of throwing NPE: throwParameterIsNullException.
-            // Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
-            // the value.
-            if (invokedMethod.name
-                == appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException.name) {
-              // We found a NPE (or similar exception) throwing code.
-              // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on the value.
+          if (instr.isInvokeStatic()) {
+            InvokeStatic invoke = instr.asInvokeStatic();
+            if (isKotlinCheckParameterIsNotNull(appView, invoke, value)) {
+              return InstructionEffect.DESIRED_EFFECT;
+            }
+            if (isKotlinThrowParameterIsNullException(appView, invoke)) {
+              // Kotlin specific way of throwing NPE. Combined with the above CONDITIONAL_EFFECT,
+              // the code acts as a null check for the given value.
               for (BasicBlock predecessor : currentBlock.getPredecessors()) {
                 if (isNullCheck(predecessor.exit(), value)) {
                   return InstructionEffect.DESIRED_EFFECT;
@@ -858,11 +855,6 @@
               // Hitting here means that this call might be used for other parameters. If we don't
               // bail out, it will be regarded as side effects for the current value.
               return InstructionEffect.NO_EFFECT;
-            } else {
-              // Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
-              assert invokedMethod.name
-                  == appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull.name;
-              return InstructionEffect.DESIRED_EFFECT;
             }
           }
           if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
@@ -912,32 +904,40 @@
   }
 
   // Note that this method may have false positives, since the application could in principle
-  // 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) {
+  // declare a method called checkParameterIsNotNull(parameter, message) in a package that starts
+  // with "kotlin".
+  private static boolean isKotlinCheckParameterIsNotNull(
+      AppView<?> appView, InvokeStatic invoke, Value value) {
     if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
       return false;
     }
-    if (!instr.isInvokeStatic()) {
+    // We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
+    // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
+    DexMethod checkParameterIsNotNullMethod =
+        appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull;
+    DexMethod originalInvokedMethod =
+        appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
+    return originalInvokedMethod.match(checkParameterIsNotNullMethod)
+        && invoke.getFirstArgument() == value
+        && originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
+  }
+
+  // Note that this method may have false positives, since the application could in principle
+  // declare a method called throwParameterIsNullException(parameterName) in a package that starts
+  // with "kotlin".
+  private static boolean isKotlinThrowParameterIsNullException(
+      AppView<?> appView, InvokeStatic invoke) {
+    if (appView.options().kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
       return false;
     }
-    // We need to strip the holder, since Kotlin adds different versions of null-check machinery,
+    // We need to ignore the holder, since Kotlin adds different versions of null-check machinery,
     // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
-    MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
-    Wrapper<DexMethod> checkParameterIsNotNull =
-        wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull);
-    Wrapper<DexMethod> throwParamIsNullException =
-        wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException);
-    DexMethod invokedMethod =
-        appView.graphLens().getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
-    Wrapper<DexMethod> methodWrap = wrapper.wrap(invokedMethod);
-    if (methodWrap.equals(throwParamIsNullException)
-        || (methodWrap.equals(checkParameterIsNotNull) && instr.inValues().get(0).equals(value))) {
-      if (invokedMethod.holder.getPackageDescriptor().startsWith(Kotlin.NAME)) {
-        return true;
-      }
-    }
-    return false;
+    DexMethod throwParameterIsNullExceptionMethod =
+        appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException;
+    DexMethod originalInvokedMethod =
+        appView.graphLens().getOriginalMethodSignature(invoke.getInvokedMethod());
+    return originalInvokedMethod.match(throwParameterIsNullExceptionMethod)
+        && originalInvokedMethod.getHolderType().getPackageDescriptor().startsWith(Kotlin.NAME);
   }
 
   private static boolean isNullCheck(Instruction instr, Value value) {
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 1402421..f776d44 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
@@ -364,7 +364,7 @@
       OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
     OneTimeMethodProcessor methodProcessor =
         OneTimeMethodProcessor.create(methodsToReprocess, appView);
-    methodProcessor.forEachWave(
+    methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingId) ->
             forEachMethod(
                 method,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
new file mode 100644
index 0000000..9484007
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateCfUtilityMethodsForCodeOptimizations.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.optimize.templates;
+
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+
+public final class CfUtilityMethodsForCodeOptimizations {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Ljava/lang/ClassCastException;");
+  }
+
+  public static CfCode
+      CfUtilityMethodsForCodeOptimizationsTemplates_throwClassCastExceptionIfNotNull(
+          InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfIf(If.Type.EQ, ValueType.OBJECT, label2),
+            label1,
+            new CfNew(options.itemFactory.createType("Ljava/lang/ClassCastException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/ClassCastException;"),
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0},
+                    new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfReturnVoid(),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index d96b5d4..0179328 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -48,9 +48,11 @@
 import com.android.tools.r8.utils.PredicateUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Sets;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Optional;
+import java.util.SortedSet;
 import java.util.function.Predicate;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
@@ -67,6 +69,7 @@
 import org.objectweb.asm.util.TraceMethodVisitor;
 
 public class CfApplicationWriter {
+
   private static final boolean RUN_VERIFIER = false;
   private static final boolean PRINT_CF = false;
 
@@ -222,7 +225,17 @@
     for (DexEncodedField field : clazz.instanceFields()) {
       writeField(field, writer);
     }
-    clazz.forEachProgramMethod(method -> writeMethod(method, version, rewriter, writer, defaults));
+    if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
+      SortedSet<ProgramMethod> programMethodSortedSet =
+          Sets.newTreeSet(
+              (a, b) -> a.getDefinition().method.slowCompareTo(b.getDefinition().method));
+      clazz.forEachProgramMethod(programMethodSortedSet::add);
+      programMethodSortedSet.forEach(
+          method -> writeMethod(method, version, rewriter, writer, defaults));
+    } else {
+      clazz.forEachProgramMethod(
+          method -> writeMethod(method, version, rewriter, writer, defaults));
+    }
     writer.visitEnd();
 
     byte[] result = writer.toByteArray();
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 2dc287a..2168235 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -207,8 +208,9 @@
     List<Value> ins = invoke.arguments();
     Value[] changes = new Value[ins.size()];
     if (isReflectionMethod(appView.dexItemFactory(), invokedMethod) || isClassNameComparison) {
-      DexReference itemBasedString = identifyIdentifier(invoke, appView);
-      if (itemBasedString == null) {
+      IdentifierNameStringLookupResult<?> identifierLookupResult =
+          identifyIdentifier(invoke, appView, code.context());
+      if (identifierLookupResult == null) {
         warnUndeterminedIdentifierIfNecessary(invokedMethod, code.context(), invoke, null);
         return iterator;
       }
@@ -225,7 +227,10 @@
       Value newIn = code.createValue(in.getType(), in.getLocalInfo());
       DexItemBasedConstString decoupled =
           new DexItemBasedConstString(
-              newIn, itemBasedString, ClassNameComputationInfo.none(), throwingInfo);
+              newIn,
+              identifierLookupResult.getReference(),
+              ClassNameComputationInfo.none(),
+              throwingInfo);
       changes[identifierPosition] = newIn;
 
       if (in.numberOfAllUsers() == 1) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index ba73522..01157b9 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -10,12 +10,15 @@
 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;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
@@ -28,6 +31,7 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -196,26 +200,28 @@
    * @return {@link DexReference} corresponding to the first constant string argument that matches a
    *     class or member name, or {@code null} if no such constant was found.
    */
-  public static DexReference identifyIdentifier(
-      InvokeMethod invoke, DexDefinitionSupplier definitions) {
+  public static IdentifierNameStringLookupResult<?> identifyIdentifier(
+      InvokeMethod invoke, DexDefinitionSupplier definitions, ProgramMethod context) {
+    DexItemFactory dexItemFactory = definitions.dexItemFactory();
     List<Value> ins = invoke.arguments();
     // The only static calls: Class#forName,
     //   which receive either (String) or (String, boolean, ClassLoader) as ins.
     if (invoke.isInvokeStatic()) {
       InvokeStatic invokeStatic = invoke.asInvokeStatic();
-      if (definitions.dexItemFactory().classMethods
-          .isReflectiveClassLookup(invokeStatic.getInvokedMethod())) {
-        return ConstantValueUtils.getDexTypeFromClassForName(invokeStatic, definitions);
+      if (dexItemFactory.classMethods.isReflectiveClassLookup(invokeStatic.getInvokedMethod())) {
+        return IdentifierNameStringLookupResult.fromClassForName(
+            ConstantValueUtils.getDexTypeFromClassForName(invokeStatic, definitions));
       }
     }
 
     if (invoke.isInvokeVirtual()) {
       InvokeVirtual invokeVirtual = invoke.asInvokeVirtual();
-      if (isClassNameComparison(invokeVirtual, definitions.dexItemFactory())) {
+      if (isClassNameComparison(invokeVirtual, dexItemFactory)) {
         int argumentIndex = getPositionOfFirstConstString(invokeVirtual);
         if (argumentIndex >= 0) {
-          return inferTypeFromConstStringValue(
-              definitions, invokeVirtual.inValues().get(argumentIndex));
+          return IdentifierNameStringLookupResult.fromClassNameComparison(
+              inferTypeFromConstStringValue(
+                  definitions, invokeVirtual.inValues().get(argumentIndex)));
         }
       }
     }
@@ -226,8 +232,7 @@
     }
 
     boolean isReferenceFieldUpdater =
-        invoke.getReturnType().descriptor
-            == definitions.dexItemFactory().referenceFieldUpdaterDescriptor;
+        invoke.getReturnType().descriptor == dexItemFactory.referenceFieldUpdaterDescriptor;
     int positionOfIdentifier = isReferenceFieldUpdater ? 2 : 1;
     Value in = ins.get(positionOfIdentifier);
     if (in.isConstString()) {
@@ -241,7 +246,7 @@
         // declared in the library. Hence there is no need to handle this case.
         return null;
       }
-      DexClass holder = definitions.definitionFor(holderType);
+      DexClass holder = definitions.definitionFor(holderType, context);
       if (holder == null) {
         return null;
       }
@@ -253,22 +258,29 @@
           return null;
         }
         DexType fieldType = fieldTypeValue.getConstInstruction().asConstClass().getValue();
-        return inferFieldInHolder(holder, dexString.toString(), fieldType);
+        return IdentifierNameStringLookupResult.fromUncategorized(
+            inferFieldInHolder(holder, dexString.toString(), fieldType));
       }
       if (numOfParams == 2) {
-        return inferFieldInHolder(holder, dexString.toString(), null);
+        return IdentifierNameStringLookupResult.fromUncategorized(
+            inferFieldInHolder(holder, dexString.toString(), null));
       }
       assert numOfParams == 3;
-      DexTypeList arguments =
-          retrieveDexTypeListFromClassList(invoke, ins.get(2), definitions.dexItemFactory());
+      DexTypeList arguments = retrieveDexTypeListFromClassList(invoke, ins.get(2), dexItemFactory);
       if (arguments == null) {
         return null;
       }
-      return inferMethodInHolder(holder, dexString.toString(), arguments);
+      return IdentifierNameStringLookupResult.fromUncategorized(
+          inferMethodInHolder(holder, dexString.toString(), arguments));
     }
     if (in.isDexItemBasedConstString()) {
       DexItemBasedConstString constString = in.getConstInstruction().asDexItemBasedConstString();
-      return constString.getItem();
+      if (constString.getItem().isDexType()) {
+        return IdentifierNameStringLookupResult.fromDexTypeBasedConstString(
+            constString.getItem().asDexType());
+      }
+      return IdentifierNameStringLookupResult.fromDexMemberBasedConstString(
+          constString.getItem().asDexMember());
     }
     return null;
   }
@@ -288,7 +300,7 @@
       AppView<AppInfoWithLiveness> appView, DexString dexString) {
     // "fully.qualified.ClassName.fieldOrMethodName"
     // "fully.qualified.ClassName#fieldOrMethodName"
-    DexReference itemBasedString = inferMemberFromNameString(appView, dexString);
+    DexMember<?, ?> itemBasedString = inferMemberFromNameString(appView, dexString);
     if (itemBasedString == null) {
       // "fully.qualified.ClassName"
       return inferTypeFromNameString(appView, dexString);
@@ -322,7 +334,7 @@
     return null;
   }
 
-  private static DexReference inferMemberFromNameString(
+  private static DexMember<?, ?> inferMemberFromNameString(
       AppView<AppInfoWithLiveness> appView, DexString dexString) {
     String identifier = dexString.toString();
     String typeIdentifier = null;
@@ -357,14 +369,14 @@
     if (holder == null) {
       return null;
     }
-    DexReference itemBasedString = inferFieldInHolder(holder, memberIdentifier, null);
+    DexMember<?, ?> itemBasedString = inferFieldInHolder(holder, memberIdentifier, null);
     if (itemBasedString == null) {
       itemBasedString = inferMethodNameInHolder(holder, memberIdentifier);
     }
     return itemBasedString;
   }
 
-  private static DexReference inferFieldInHolder(DexClass holder, String name, DexType fieldType) {
+  private static DexField inferFieldInHolder(DexClass holder, String name, DexType fieldType) {
     for (DexEncodedField encodedField : holder.fields()) {
       if (encodedField.field.name.toString().equals(name)
           && (fieldType == null || encodedField.field.type == fieldType)) {
@@ -374,7 +386,7 @@
     return null;
   }
 
-  private static DexReference inferMethodNameInHolder(DexClass holder, String name) {
+  private static DexMethod inferMethodNameInHolder(DexClass holder, String name) {
     for (DexEncodedMethod encodedMethod : holder.methods()) {
       if (encodedMethod.method.name.toString().equals(name)) {
         return encodedMethod.method;
@@ -383,7 +395,7 @@
     return null;
   }
 
-  private static DexReference inferMethodInHolder(
+  private static DexMethod inferMethodInHolder(
       DexClass holder, String name, DexTypeList arguments) {
     assert arguments != null;
     for (DexEncodedMethod encodedMethod : holder.methods()) {
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..2498026
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassForNameIdentifierNameStringLookupResult.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class ClassForNameIdentifierNameStringLookupResult
+    extends IdentifierNameStringTypeLookupResult {
+
+  ClassForNameIdentifierNameStringLookupResult(DexType type) {
+    super(type);
+  }
+
+  @Override
+  public boolean isTypeInitializedFromUse() {
+    return true;
+  }
+
+  @Override
+  public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+    return options.isForceProguardCompatibilityEnabled();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..0305995
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/ClassNameComparisonIdentifierNameStringLookupResult.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class ClassNameComparisonIdentifierNameStringLookupResult
+    extends IdentifierNameStringTypeLookupResult {
+
+  ClassNameComparisonIdentifierNameStringLookupResult(DexType type) {
+    super(type);
+  }
+
+  @Override
+  public boolean isTypeInitializedFromUse() {
+    return false;
+  }
+
+  @Override
+  public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexMemberBasedConstStringIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexMemberBasedConstStringIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..5072d35
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexMemberBasedConstStringIdentifierNameStringLookupResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexMember;
+
+public class DexMemberBasedConstStringIdentifierNameStringLookupResult
+    extends IdentifierNameStringMemberLookupResult {
+
+  DexMemberBasedConstStringIdentifierNameStringLookupResult(DexMember<?, ?> member) {
+    super(member);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..f2c1ec1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/DexTypeBasedConstStringIdentifierNameStringLookupResult.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class DexTypeBasedConstStringIdentifierNameStringLookupResult
+    extends IdentifierNameStringTypeLookupResult {
+
+  DexTypeBasedConstStringIdentifierNameStringLookupResult(DexType type) {
+    super(type);
+  }
+
+  @Override
+  public boolean isTypeInitializedFromUse() {
+    return false;
+  }
+
+  @Override
+  public boolean isTypeInstantiatedFromUse(InternalOptions options) {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..2c11771
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringLookupResult.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import static com.android.tools.r8.utils.FunctionUtils.applyOrElse;
+
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+
+public abstract class IdentifierNameStringLookupResult<R extends DexReference> {
+
+  private final R reference;
+
+  IdentifierNameStringLookupResult(R reference) {
+    assert reference != null;
+    this.reference = reference;
+  }
+
+  public static ClassForNameIdentifierNameStringLookupResult fromClassForName(DexType type) {
+    return applyOrElse(type, ClassForNameIdentifierNameStringLookupResult::new, null);
+  }
+
+  public static ClassNameComparisonIdentifierNameStringLookupResult fromClassNameComparison(
+      DexType type) {
+    return applyOrElse(type, ClassNameComparisonIdentifierNameStringLookupResult::new, null);
+  }
+
+  public static DexTypeBasedConstStringIdentifierNameStringLookupResult fromDexTypeBasedConstString(
+      DexType type) {
+    return applyOrElse(type, DexTypeBasedConstStringIdentifierNameStringLookupResult::new, null);
+  }
+
+  public static DexMemberBasedConstStringIdentifierNameStringLookupResult
+      fromDexMemberBasedConstString(DexMember<?, ?> member) {
+    return applyOrElse(
+        member, DexMemberBasedConstStringIdentifierNameStringLookupResult::new, null);
+  }
+
+  public static UncategorizedMemberIdentifierNameStringLookupResult fromUncategorized(
+      DexMember<?, ?> member) {
+    return applyOrElse(member, UncategorizedMemberIdentifierNameStringLookupResult::new, null);
+  }
+
+  public boolean isTypeResult() {
+    return false;
+  }
+
+  public IdentifierNameStringTypeLookupResult asTypeResult() {
+    return null;
+  }
+
+  public R getReference() {
+    return reference;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringMemberLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringMemberLookupResult.java
new file mode 100644
index 0000000..61d7925
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringMemberLookupResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexMember;
+
+public abstract class IdentifierNameStringMemberLookupResult
+    extends IdentifierNameStringLookupResult<DexMember<?, ?>> {
+
+  IdentifierNameStringMemberLookupResult(DexMember<?, ?> member) {
+    super(member);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
new file mode 100644
index 0000000..6707406
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/IdentifierNameStringTypeLookupResult.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+
+public abstract class IdentifierNameStringTypeLookupResult
+    extends IdentifierNameStringLookupResult<DexType> {
+
+  IdentifierNameStringTypeLookupResult(DexType type) {
+    super(type);
+  }
+
+  public abstract boolean isTypeInitializedFromUse();
+
+  public abstract boolean isTypeInstantiatedFromUse(InternalOptions options);
+
+  @Override
+  public boolean isTypeResult() {
+    return true;
+  }
+
+  @Override
+  public IdentifierNameStringTypeLookupResult asTypeResult() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/identifiernamestring/UncategorizedMemberIdentifierNameStringLookupResult.java b/src/main/java/com/android/tools/r8/naming/identifiernamestring/UncategorizedMemberIdentifierNameStringLookupResult.java
new file mode 100644
index 0000000..5d29fc1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/identifiernamestring/UncategorizedMemberIdentifierNameStringLookupResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.identifiernamestring;
+
+import com.android.tools.r8.graph.DexMember;
+
+public class UncategorizedMemberIdentifierNameStringLookupResult
+    extends IdentifierNameStringMemberLookupResult {
+
+  UncategorizedMemberIdentifierNameStringLookupResult(DexMember<?, ?> member) {
+    super(member);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 55c58c6cd..0922f50 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
@@ -21,12 +22,15 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.optimize.PublicizerLens.PublicizedLensBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence.Wrapper;
 import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -159,13 +163,28 @@
       }
     }
 
-    if (!accessFlags.isPrivate() || appView.dexItemFactory().isConstructor(method.getReference())) {
-      // TODO(b/150589374): This should check for dispatch targets or just abandon in
-      //  package-private.
+    if (method.getDefinition().isInstanceInitializer() || accessFlags.isProtected()) {
       doPublicize(method);
       return false;
     }
 
+    if (accessFlags.isPackagePrivate()) {
+      // If we publicize a package private method we have to ensure there is no overrides of it. We
+      // could potentially publicize a method if it only has package-private overrides, but for know
+      // we just check if it is seen below.
+      // Note that we will not publize private methods if there exists a package-private override,
+      // and there is therefore no need to check the hierarchy above.
+      MemberPool<DexMethod> memberPool = methodPoolCollection.get(method.getHolder());
+      Wrapper<DexMethod> methodKey = MethodSignatureEquivalence.get().wrap(method.getReference());
+      if (memberPool.hasSeenStrictlyBelow(methodKey)) {
+        return false;
+      }
+      doPublicize(method);
+      return false;
+    }
+
+    assert accessFlags.isPrivate();
+
     if (accessFlags.isStatic()) {
       // For private static methods we can just relax the access to public, since
       // even though JLS prevents from declaring static method in derived class if
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 a81b5dc..6922939 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -161,12 +161,15 @@
   /** All types that *must* never be inlined due to a configuration directive (testing only). */
   public final Set<DexType> neverClassInline;
 
+  private final Set<DexType> noUnusedInterfaceRemoval;
   private final Set<DexType> noVerticalClassMerging;
   private final Set<DexType> noHorizontalClassMerging;
   private final Set<DexType> noStaticClassMerging;
 
-  /** Set of const-class references. */
-  public final Set<DexType> constClassReferences;
+  /**
+   * Set of lock candidates (i.e., types whose class reference may flow to a monitor instruction).
+   */
+  public final Set<DexType> lockCandidates;
   /**
    * A map from seen init-class references to the minimum required visibility of the corresponding
    * static field.
@@ -226,6 +229,7 @@
       Set<DexMethod> neverReprocess,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
+      Set<DexType> noUnusedInterfaceRemoval,
       Set<DexType> noVerticalClassMerging,
       Set<DexType> noHorizontalClassMerging,
       Set<DexType> noStaticClassMerging,
@@ -234,7 +238,7 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps,
-      Set<DexType> constClassReferences,
+      Set<DexType> lockCandidates,
       Map<DexType, Visibility> initClassReferences) {
     super(syntheticItems, classToFeatureSplitMap, mainDexClasses);
     this.deadProtoTypes = deadProtoTypes;
@@ -265,6 +269,7 @@
     this.neverReprocess = neverReprocess;
     this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
+    this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
     this.noVerticalClassMerging = noVerticalClassMerging;
     this.noHorizontalClassMerging = noHorizontalClassMerging;
     this.noStaticClassMerging = noStaticClassMerging;
@@ -273,7 +278,7 @@
     this.prunedTypes = prunedTypes;
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
-    this.constClassReferences = constClassReferences;
+    this.lockCandidates = lockCandidates;
     this.initClassReferences = initClassReferences;
   }
 
@@ -307,6 +312,7 @@
       Set<DexMethod> neverReprocess,
       PredicateSet<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
+      Set<DexType> noUnusedInterfaceRemoval,
       Set<DexType> noVerticalClassMerging,
       Set<DexType> noHorizontalClassMerging,
       Set<DexType> noStaticClassMerging,
@@ -315,7 +321,7 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       EnumValueInfoMapCollection enumValueInfoMaps,
-      Set<DexType> constClassReferences,
+      Set<DexType> lockCandidates,
       Map<DexType, Visibility> initClassReferences) {
     super(
         appInfoWithClassHierarchy.getSyntheticItems().commit(appInfoWithClassHierarchy.app()),
@@ -349,6 +355,7 @@
     this.neverReprocess = neverReprocess;
     this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
+    this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
     this.noVerticalClassMerging = noVerticalClassMerging;
     this.noHorizontalClassMerging = noHorizontalClassMerging;
     this.noStaticClassMerging = noStaticClassMerging;
@@ -357,7 +364,7 @@
     this.prunedTypes = prunedTypes;
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
-    this.constClassReferences = constClassReferences;
+    this.lockCandidates = lockCandidates;
     this.initClassReferences = initClassReferences;
   }
 
@@ -396,6 +403,7 @@
         previous.neverReprocess,
         previous.alwaysClassInline,
         previous.neverClassInline,
+        previous.noUnusedInterfaceRemoval,
         previous.noVerticalClassMerging,
         previous.noHorizontalClassMerging,
         previous.noStaticClassMerging,
@@ -404,7 +412,7 @@
         previous.prunedTypes,
         previous.switchMaps,
         previous.enumValueInfoMaps,
-        previous.constClassReferences,
+        previous.lockCandidates,
         previous.initClassReferences);
   }
 
@@ -447,6 +455,7 @@
         previous.neverReprocess,
         previous.alwaysClassInline,
         previous.neverClassInline,
+        previous.noUnusedInterfaceRemoval,
         previous.noVerticalClassMerging,
         previous.noHorizontalClassMerging,
         previous.noStaticClassMerging,
@@ -457,7 +466,7 @@
             : CollectionUtils.mergeSets(previous.prunedTypes, removedClasses),
         previous.switchMaps,
         previous.enumValueInfoMaps,
-        previous.constClassReferences,
+        previous.lockCandidates,
         previous.initClassReferences);
     assert keepInfo.verifyNoneArePinned(removedClasses, previous);
   }
@@ -535,6 +544,7 @@
     this.neverReprocess = previous.neverReprocess;
     this.alwaysClassInline = previous.alwaysClassInline;
     this.neverClassInline = previous.neverClassInline;
+    this.noUnusedInterfaceRemoval = previous.noUnusedInterfaceRemoval;
     this.noVerticalClassMerging = previous.noVerticalClassMerging;
     this.noHorizontalClassMerging = previous.noHorizontalClassMerging;
     this.noStaticClassMerging = previous.noStaticClassMerging;
@@ -543,7 +553,7 @@
     this.prunedTypes = previous.prunedTypes;
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
-    this.constClassReferences = previous.constClassReferences;
+    this.lockCandidates = previous.lockCandidates;
     this.initClassReferences = previous.initClassReferences;
     previous.markObsolete();
   }
@@ -700,7 +710,7 @@
    * merge any const-class classes. More info at b/142438687.
    */
   public boolean isLockCandidate(DexType type) {
-    return constClassReferences.contains(type);
+    return lockCandidates.contains(type);
   }
 
   public Set<DexType> getDeadProtoTypes() {
@@ -1033,6 +1043,7 @@
         lens.rewriteMethods(neverReprocess),
         alwaysClassInline.rewriteItems(lens::lookupType),
         lens.rewriteTypes(neverClassInline),
+        lens.rewriteTypes(noUnusedInterfaceRemoval),
         lens.rewriteTypes(noVerticalClassMerging),
         lens.rewriteTypes(noHorizontalClassMerging),
         lens.rewriteTypes(noStaticClassMerging),
@@ -1042,7 +1053,7 @@
         prunedTypes,
         lens.rewriteFieldKeys(switchMaps),
         enumValueInfoMaps.rewrittenWithLens(lens),
-        lens.rewriteTypes(constClassReferences),
+        lens.rewriteTypes(lockCandidates),
         lens.rewriteTypeKeys(initClassReferences));
   }
 
@@ -1403,6 +1414,11 @@
         .shouldBreak();
   }
 
+  /** All unused interface types that *must* never be pruned. */
+  public Set<DexType> getNoUnusedInterfaceRemovalSet() {
+    return noUnusedInterfaceRemoval;
+  }
+
   /**
    * All types that *must* never be merged vertically due to a configuration directive (testing
    * only).
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index b67d90d..18ae137 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
 
 public class DefaultEnqueuerUseRegistry extends UseRegistry {
 
@@ -114,8 +116,9 @@
   }
 
   @Override
-  public void registerConstClass(DexType type) {
-    enqueuer.traceConstClass(type, context);
+  public void registerConstClass(
+      DexType type, ListIterator<? extends CfOrDexInstruction> iterator) {
+    enqueuer.traceConstClass(type, context, iterator);
   }
 
   @Override
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 7a2969e..8e6142e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -10,6 +10,9 @@
 import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
 
 import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
@@ -31,6 +34,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.ClassMethods;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
@@ -87,6 +91,8 @@
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
+import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringTypeLookupResult;
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
 import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
@@ -101,6 +107,7 @@
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
@@ -131,6 +138,7 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -314,10 +322,10 @@
   private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
 
   /**
-   * A set of seen const-class references that both serve as an initial lock-candidate set and will
-   * prevent statically merging the classes referenced.
+   * A set of seen const-class references that serve as an initial lock-candidate set and will
+   * prevent class merging.
    */
-  private final Set<DexType> constClassReferences = Sets.newIdentityHashSet();
+  private final Set<DexType> lockCandidates = Sets.newIdentityHashSet();
 
   /**
    * A map from seen init-class references to the minimum required visibility of the corresponding
@@ -535,9 +543,22 @@
   }
 
   private DexClass definitionFor(DexType type) {
-    DexClass clazz = appView.definitionFor(type);
+    return internalDefinitionFor(type, false);
+  }
+
+  private DexClass definitionForFromReflectiveAccess(DexType type) {
+    return internalDefinitionFor(type, true);
+  }
+
+  private DexClass internalDefinitionFor(DexType type, boolean fromReflectiveAccess) {
+    DexClass clazz =
+        fromReflectiveAccess
+            ? appView.appInfo().definitionForWithoutExistenceAssert(type)
+            : appView.appInfo().definitionFor(type);
     if (clazz == null) {
-      reportMissingClass(type);
+      if (!fromReflectiveAccess) {
+        reportMissingClass(type);
+      }
       return null;
     }
     if (clazz.isNotProgramClass()) {
@@ -610,9 +631,9 @@
   }
 
   private DexProgramClass getProgramClassOrNullFromReflectiveAccess(DexType type) {
-    // This is using appInfo.definitionForWithoutExistenceAssert() to avoid that we report
-    // reflectively accessed types as missing.
-    return asProgramClassOrNull(appInfo().definitionForWithoutExistenceAssert(type));
+    // To avoid that we report reflectively accessed types as missing.
+    DexClass clazz = definitionForFromReflectiveAccess(type);
+    return clazz != null && clazz.isProgramClass() ? clazz.asProgramClass() : null;
   }
 
   private void warnIfLibraryTypeInheritsFromProgramType(DexLibraryClass clazz) {
@@ -926,17 +947,63 @@
     traceConstClassOrCheckCast(type, currentMethod);
   }
 
-  void traceConstClass(DexType type, ProgramMethod currentMethod) {
+  void traceConstClass(
+      DexType type,
+      ProgramMethod currentMethod,
+      ListIterator<? extends CfOrDexInstruction> iterator) {
+    handleLockCandidate(type, currentMethod, iterator);
+    traceConstClassOrCheckCast(type, currentMethod);
+  }
+
+  private void handleLockCandidate(
+      DexType type,
+      ProgramMethod currentMethod,
+      ListIterator<? extends CfOrDexInstruction> iterator) {
     // We conservatively group T.class and T[].class to ensure that we do not merge T with S if
     // potential locks on T[].class and S[].class exists.
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
       DexProgramClass baseClass = getProgramClassOrNull(baseType);
-      if (baseClass != null) {
-        constClassReferences.add(baseType);
+      if (baseClass != null && isConstClassMaybeUsedAsLock(currentMethod, iterator)) {
+        lockCandidates.add(baseType);
       }
     }
-    traceConstClassOrCheckCast(type, currentMethod);
+  }
+
+  /**
+   * Returns true if the const-class value may flow into a monitor instruction.
+   *
+   * <p>Some common usages of const-class values are handled, such as calls to Class.get*Name().
+   */
+  private boolean isConstClassMaybeUsedAsLock(
+      ProgramMethod currentMethod, ListIterator<? extends CfOrDexInstruction> iterator) {
+    if (iterator == null) {
+      return true;
+    }
+    boolean result = true;
+    if (currentMethod.getDefinition().getCode().isCfCode()) {
+      CfInstruction nextInstruction =
+          IteratorUtils.nextUntil(
+                  iterator,
+                  instruction ->
+                      !instruction.asCfInstruction().isLabel()
+                          && !instruction.asCfInstruction().isPosition())
+              .asCfInstruction();
+      assert nextInstruction != null;
+      if (nextInstruction.isInvoke()) {
+        CfInvoke invoke = nextInstruction.asInvoke();
+        DexMethod invokedMethod = invoke.getMethod();
+        ClassMethods classMethods = appView.dexItemFactory().classMethods;
+        if (classMethods.isReflectiveNameLookup(invokedMethod)
+            || invokedMethod == classMethods.desiredAssertionStatus
+            || invokedMethod == classMethods.getClassLoader
+            || invokedMethod == classMethods.getPackage) {
+          result = false;
+        }
+      }
+      iterator.previous();
+    }
+    return result;
   }
 
   private void traceConstClassOrCheckCast(DexType type, ProgramMethod currentMethod) {
@@ -1527,14 +1594,16 @@
       // Ignore primitive types.
       return;
     }
-    DexProgramClass holder = getProgramClassOrNull(type);
-    if (holder == null) {
+    DexProgramClass clazz = getProgramClassOrNull(type);
+    if (clazz == null) {
       return;
     }
-    markTypeAsLive(
-        holder,
-        scopedMethodsForLiveTypes.computeIfAbsent(type, ignore -> new ScopedDexMethodSet()),
-        graphReporter.registerClass(holder, reason));
+    markTypeAsLive(clazz, reason);
+  }
+
+  private void markTypeAsLive(DexProgramClass clazz, KeepReason reason) {
+    assert clazz != null;
+    markTypeAsLive(clazz, graphReporter.registerClass(clazz, reason));
   }
 
   private void markTypeAsLive(DexType type, Function<DexProgramClass, KeepReasonWitness> reason) {
@@ -1671,7 +1740,9 @@
       return;
     }
 
-    if (!appView.options().enableUnusedInterfaceRemoval || mode.isTracingMainDex()) {
+    if (!appView.options().enableUnusedInterfaceRemoval
+        || rootSet.noUnusedInterfaceRemoval.contains(type)
+        || mode.isTracingMainDex()) {
       markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, implementer));
     } else {
       if (liveTypes.contains(clazz)) {
@@ -3129,6 +3200,7 @@
             rootSet.neverReprocess,
             rootSet.alwaysClassInline,
             rootSet.neverClassInline,
+            rootSet.noUnusedInterfaceRemoval,
             rootSet.noVerticalClassMerging,
             rootSet.noHorizontalClassMerging,
             rootSet.noStaticClassMerging,
@@ -3137,7 +3209,7 @@
             Collections.emptySet(),
             Collections.emptyMap(),
             EnumValueInfoMapCollection.empty(),
-            constClassReferences,
+            lockCandidates,
             initClassReferences);
     appInfo.markObsolete();
     return appInfoWithLiveness;
@@ -3758,18 +3830,22 @@
     if (!isReflectionMethod(dexItemFactory, invokedMethod)) {
       return;
     }
-    DexReference identifierItem = identifyIdentifier(invoke, appView);
-    if (identifierItem == null) {
+    IdentifierNameStringLookupResult<?> identifierLookupResult =
+        identifyIdentifier(invoke, appView, method);
+    if (identifierLookupResult == null) {
       return;
     }
-    if (identifierItem.isDexType()) {
-      DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(identifierItem.asDexType());
+    DexReference referencedItem = identifierLookupResult.getReference();
+    if (referencedItem.isDexType()) {
+      assert identifierLookupResult.isTypeResult();
+      IdentifierNameStringTypeLookupResult identifierTypeLookupResult =
+          identifierLookupResult.asTypeResult();
+      DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(referencedItem.asDexType());
       if (clazz == null) {
         return;
       }
-      if (clazz.isAnnotation() || clazz.isInterface()) {
-        markTypeAsLive(clazz.type, KeepReason.reflectiveUseIn(method));
-      } else {
+      markTypeAsLive(clazz, KeepReason.reflectiveUseIn(method));
+      if (identifierTypeLookupResult.isTypeInstantiatedFromUse(options)) {
         workList.enqueueMarkInstantiatedAction(
             clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
         if (clazz.hasDefaultInitializer()) {
@@ -3778,9 +3854,11 @@
           markMethodAsTargeted(initializer, reason);
           markDirectStaticOrConstructorMethodAsLive(initializer, reason);
         }
+      } else if (identifierTypeLookupResult.isTypeInitializedFromUse()) {
+        markDirectAndIndirectClassInitializersAsLive(clazz);
       }
-    } else if (identifierItem.isDexField()) {
-      DexField field = identifierItem.asDexField();
+    } else if (referencedItem.isDexField()) {
+      DexField field = referencedItem.asDexField();
       DexProgramClass clazz = getProgramClassOrNull(field.holder);
       if (clazz == null) {
         return;
@@ -3807,8 +3885,8 @@
         markFieldAsKept(programField, KeepReason.reflectiveUseIn(method));
       }
     } else {
-      assert identifierItem.isDexMethod();
-      DexMethod targetedMethodReference = identifierItem.asDexMethod();
+      assert referencedItem.isDexMethod();
+      DexMethod targetedMethodReference = referencedItem.asDexMethod();
       DexProgramClass clazz = getProgramClassOrNull(targetedMethodReference.holder);
       if (clazz == null) {
         return;
@@ -3872,8 +3950,9 @@
     }
 
     InvokeVirtual constructorDefinition = constructorValue.definition.asInvokeVirtual();
-    if (constructorDefinition.getInvokedMethod()
-        != appView.dexItemFactory().classMethods.getDeclaredConstructor) {
+    DexMethod invokedMethod = constructorDefinition.getInvokedMethod();
+    if (invokedMethod != appView.dexItemFactory().classMethods.getConstructor
+        && invokedMethod != appView.dexItemFactory().classMethods.getDeclaredConstructor) {
       // Give up, we can't tell which constructor is being invoked.
       return;
     }
@@ -3887,7 +3966,7 @@
       return;
     }
 
-    DexProgramClass clazz = getProgramClassOrNull(instantiatedType);
+    DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(instantiatedType);
     if (clazz == null) {
       return;
     }
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 2bc8459..c368c21 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -176,7 +176,7 @@
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.traceConstClass(type, context);
+      enqueuer.traceConstClass(type, context, null);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
index 76b6d8a..6383473 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
@@ -6,8 +6,8 @@
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
@@ -76,6 +76,17 @@
     this.classes = Sets.union(roots, dependencies);
   }
 
+  public boolean canReferenceItemFromContextWithoutIncreasingMainDexSize(
+      ProgramDefinition item, ProgramDefinition context) {
+    // If the context is not a root, then additional references from inside the context will not
+    // increase the size of the main dex.
+    if (!isRoot(context)) {
+      return true;
+    }
+    // Otherwise, require that the item is a root itself.
+    return isRoot(item);
+  }
+
   public boolean isEmpty() {
     assert !roots.isEmpty() || dependencies.isEmpty();
     return roots.isEmpty();
@@ -93,8 +104,8 @@
     return classes;
   }
 
-  public boolean contains(DexProgramClass clazz) {
-    return contains(clazz.type);
+  public boolean contains(ProgramDefinition clazz) {
+    return contains(clazz.getContextType());
   }
 
   public boolean contains(DexType type) {
@@ -111,6 +122,14 @@
         });
   }
 
+  public boolean isRoot(ProgramDefinition definition) {
+    return getRoots().contains(definition.getContextType());
+  }
+
+  public boolean isDependency(ProgramDefinition definition) {
+    return getDependencies().contains(definition.getContextType());
+  }
+
   public MainDexTracingResult prunedCopy(AppInfoWithLiveness appInfo) {
     Builder builder = builder(appInfo);
     Predicate<DexType> wasPruned = appInfo::wasPruned;
diff --git a/src/main/java/com/android/tools/r8/shaking/NoUnusedInterfaceRemovalRule.java b/src/main/java/com/android/tools/r8/shaking/NoUnusedInterfaceRemovalRule.java
new file mode 100644
index 0000000..c14f3e8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/NoUnusedInterfaceRemovalRule.java
@@ -0,0 +1,83 @@
+// 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.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class NoUnusedInterfaceRemovalRule extends ProguardConfigurationRule {
+
+  public static final String RULE_NAME = "nounusedinterfaceremoval";
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<NoUnusedInterfaceRemovalRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public NoUnusedInterfaceRemovalRule build() {
+      return new NoUnusedInterfaceRemovalRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  private NoUnusedInterfaceRemovalRule(
+      Origin origin,
+      Position position,
+      String source,
+      List<ProguardTypeMatcher> classAnnotations,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return RULE_NAME;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index d846f49..f71d027 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -469,6 +469,11 @@
           configurationBuilder.addRule(rule);
           return true;
         }
+        if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) {
+          ProguardConfigurationRule rule = parseNoUnusedInterfaceRemovalRule(optionStart);
+          configurationBuilder.addRule(rule);
+          return true;
+        }
         if (acceptString(NoVerticalClassMergingRule.RULE_NAME)) {
           ProguardConfigurationRule rule = parseNoVerticalClassMergingRule(optionStart);
           configurationBuilder.addRule(rule);
@@ -751,6 +756,17 @@
       return keepRuleBuilder.build();
     }
 
+    private NoUnusedInterfaceRemovalRule parseNoUnusedInterfaceRemovalRule(Position start)
+        throws ProguardRuleParserException {
+      NoUnusedInterfaceRemovalRule.Builder keepRuleBuilder =
+          NoUnusedInterfaceRemovalRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
+      return keepRuleBuilder.build();
+    }
+
     private NoVerticalClassMergingRule parseNoVerticalClassMergingRule(Position start)
         throws ProguardRuleParserException {
       NoVerticalClassMergingRule.Builder keepRuleBuilder =
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 0798093..5199435 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -99,6 +99,7 @@
   private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet();
   private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
   private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
+  private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet();
   private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet();
   private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet();
   private final Set<DexType> noStaticClassMerging = Sets.newIdentityHashSet();
@@ -246,6 +247,7 @@
         || rule instanceof WhyAreYouNotInliningRule) {
       markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
     } else if (rule instanceof ClassInlineRule
+        || rule instanceof NoUnusedInterfaceRemovalRule
         || rule instanceof NoVerticalClassMergingRule
         || rule instanceof NoHorizontalClassMergingRule
         || rule instanceof NoStaticClassMergingRule
@@ -353,6 +355,7 @@
         neverReprocess,
         alwaysClassInline,
         neverClassInline,
+        noUnusedInterfaceRemoval,
         noVerticalClassMerging,
         noHorizontalClassMerging,
         noStaticClassMerging,
@@ -1238,6 +1241,9 @@
           throw new Unreachable();
       }
       context.markAsUsed();
+    } else if (context instanceof NoUnusedInterfaceRemovalRule) {
+      noUnusedInterfaceRemoval.add(item.asDexClass().type);
+      context.markAsUsed();
     } else if (context instanceof NoVerticalClassMergingRule) {
       noVerticalClassMerging.add(item.asDexClass().type);
       context.markAsUsed();
@@ -1751,6 +1757,7 @@
     public final Set<DexMethod> reprocess;
     public final Set<DexMethod> neverReprocess;
     public final PredicateSet<DexType> alwaysClassInline;
+    public final Set<DexType> noUnusedInterfaceRemoval;
     public final Set<DexType> noVerticalClassMerging;
     public final Set<DexType> noHorizontalClassMerging;
     public final Set<DexType> noStaticClassMerging;
@@ -1778,6 +1785,7 @@
         Set<DexMethod> neverReprocess,
         PredicateSet<DexType> alwaysClassInline,
         Set<DexType> neverClassInline,
+        Set<DexType> noUnusedInterfaceRemoval,
         Set<DexType> noVerticalClassMerging,
         Set<DexType> noHorizontalClassMerging,
         Set<DexType> noStaticClassMerging,
@@ -1812,6 +1820,7 @@
       this.reprocess = reprocess;
       this.neverReprocess = neverReprocess;
       this.alwaysClassInline = alwaysClassInline;
+      this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
       this.noVerticalClassMerging = noVerticalClassMerging;
       this.noHorizontalClassMerging = noHorizontalClassMerging;
       this.noStaticClassMerging = noStaticClassMerging;
@@ -1893,6 +1902,7 @@
     }
 
     public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) {
+      pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer);
       pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
       pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
       pruneDeadReferences(noStaticClassMerging, definitions, enqueuer);
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 974fdb1..fe5a13a 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -337,7 +337,7 @@
         // We are not allowed to merge synchronized classes with synchronized methods.
         return;
       }
-      if (appView.appInfo().constClassReferences.contains(clazz.type)) {
+      if (appView.appInfo().lockCandidates.contains(clazz.type)) {
         // Since the type is const-class referenced (and the static merger does not create a lens
         // to map the merged type) the class will likely remain and there is no gain from merging.
         return;
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 b50d089..9831468 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -371,7 +371,9 @@
       }
       return false;
     }
-    if (targetClass.isSerializable(appView) && !appInfo.isSerializable(sourceClass.type)) {
+    if (!sourceClass.isInterface()
+        && targetClass.isSerializable(appView)
+        && !appInfo.isSerializable(sourceClass.type)) {
       // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
       //   1.10 The Serializable Interface
       //   ...
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 3625fbb..303df06 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexClasses;
 import java.util.Comparator;
@@ -37,10 +38,11 @@
   private final DexType inputContextType;
   private final Origin inputContextOrigin;
 
-  static SynthesizingContext fromNonSyntheticInputClass(DexProgramClass clazz) {
+  static SynthesizingContext fromNonSyntheticInputContext(ProgramDefinition context) {
     // A context that is itself non-synthetic is the single context, thus both the input context
     // and synthesizing context coincide.
-    return new SynthesizingContext(clazz.type, clazz.type, clazz.origin);
+    return new SynthesizingContext(
+        context.getContextType(), context.getContextType(), context.getOrigin());
   }
 
   static SynthesizingContext fromSyntheticInputClass(
@@ -73,7 +75,7 @@
     return inputContextOrigin;
   }
 
-  DexType createHygienicType(int syntheticId, DexItemFactory factory) {
+  DexType createHygienicType(String syntheticId, DexItemFactory factory) {
     // If the context is a synthetic input, then use its annotated context as the hygienic context.
     String contextDesc = synthesizingContextType.toDescriptorString();
     String prefix = contextDesc.substring(0, contextDesc.length() - 1);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 1d037cc..30d232e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -26,7 +26,7 @@
 
   abstract DexProgramClass getHolder();
 
-  abstract HashCode computeHash();
+  abstract HashCode computeHash(boolean intermediate);
 
-  abstract boolean isEquivalentTo(SyntheticDefinition other);
+  abstract boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate);
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 8aaeeb9..1a96106 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -98,13 +97,10 @@
         lookupSyntheticMethodDefinitions(application);
 
     Collection<List<SyntheticMethodDefinition>> potentialEquivalences =
-        // Don't share synthetics in intermediate mode builds.
-        options.intermediate
-            ? ListUtils.map(methodDefinitions, Collections::singletonList)
-            : computePotentialEquivalences(methodDefinitions);
+        computePotentialEquivalences(methodDefinitions, options.intermediate);
 
     Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
-        computeActualEquivalences(potentialEquivalences, options.itemFactory);
+        computeActualEquivalences(potentialEquivalences, options.intermediate, options.itemFactory);
 
     Builder lensBuilder = NestedGraphLens.builder();
     List<DexProgramClass> newProgramClasses = new ArrayList<>();
@@ -336,11 +332,11 @@
 
   private static <T extends SyntheticDefinition & Comparable<T>>
       Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
-          Collection<List<T>> potentialEquivalences, DexItemFactory factory) {
+          Collection<List<T>> potentialEquivalences, boolean intermediate, DexItemFactory factory) {
     Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
     potentialEquivalences.forEach(
         members -> {
-          List<List<T>> groups = groupEquivalent(members);
+          List<List<T>> groups = groupEquivalent(members, intermediate);
           for (List<T> group : groups) {
             T representative = findDeterministicRepresentative(group);
             // The representative is required to be the first element of the group.
@@ -360,6 +356,9 @@
           groups.sort(EquivalenceGroup::compareTo);
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
+            // Two equivalence groups in same context type must be distinct otherwise the assignment
+            // of the synthetic name will be non-deterministic between the two.
+            assert i == 0 || checkGroupsAreDistict(groups.get(i - 1), group);
             DexType representativeType = createExternalType(context, i, factory);
             equivalences.put(representativeType, group);
           }
@@ -368,13 +367,13 @@
   }
 
   private static <T extends SyntheticDefinition & Comparable<T>> List<List<T>> groupEquivalent(
-      List<T> potentialEquivalence) {
+      List<T> potentialEquivalence, boolean intermediate) {
     List<List<T>> groups = new ArrayList<>();
     // Each other member is in a shared group if it is actually equivalent to the first member.
     for (T synthetic : potentialEquivalence) {
       boolean requireNewGroup = true;
       for (List<T> group : groups) {
-        if (synthetic.isEquivalentTo(group.get(0))) {
+        if (synthetic.isEquivalentTo(group.get(0), intermediate)) {
           requireNewGroup = false;
           group.add(synthetic);
           break;
@@ -389,6 +388,12 @@
     return groups;
   }
 
+  private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistict(
+      EquivalenceGroup<T> g1, EquivalenceGroup<T> g2) {
+    assert g1.compareTo(g2) != 0;
+    return true;
+  }
+
   private static <T extends SyntheticDefinition & Comparable<T>> T findDeterministicRepresentative(
       List<T> members) {
     // Pick a deterministic member as representative.
@@ -412,10 +417,10 @@
   }
 
   private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences(
-      List<T> definitions) {
+      List<T> definitions, boolean intermediate) {
     Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
     for (T definition : definitions) {
-      HashCode hash = definition.computeHash();
+      HashCode hash = definition.computeHash(intermediate);
       equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
     }
     return equivalences.values();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index c6726e1..dcdaafc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -16,7 +16,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -32,6 +34,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Supplier;
 
 public class SyntheticItems implements SyntheticDefinitionsProvider {
 
@@ -175,17 +178,12 @@
 
   // Internal synthetic id creation helpers.
 
-  private synchronized int getNextSyntheticId() {
+  private synchronized String getNextSyntheticId() {
     if (nextSyntheticId == INVALID_ID_AFTER_SYNTHETIC_FINALIZATION) {
       throw new InternalCompilerError(
           "Unexpected attempt to synthesize classes after synthetic finalization.");
     }
-    return nextSyntheticId++;
-  }
-
-  private static DexType hygienicType(
-      DexItemFactory factory, int syntheticId, SynthesizingContext context) {
-    return context.createHygienicType(syntheticId, factory);
+    return Integer.toString(nextSyntheticId++);
   }
 
   // Predicates and accessors.
@@ -244,15 +242,15 @@
     return Collections.unmodifiableCollection(legacyPendingClasses.values());
   }
 
-  private SynthesizingContext getSynthesizingContext(DexProgramClass context) {
-    SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.type);
+  private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
+    SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.getContextType());
     if (pendingItemContext != null) {
       return pendingItemContext.getContext();
     }
-    SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.type);
+    SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.getContextType());
     return committedItemContext != null
         ? committedItemContext.getContext()
-        : SynthesizingContext.fromNonSyntheticInputClass(context);
+        : SynthesizingContext.fromNonSyntheticInputContext(context);
   }
 
   // Addition and creation of synthetic items.
@@ -267,11 +265,28 @@
 
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
-      DexProgramClass context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
+      ProgramDefinition context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
+    return createMethod(context, factory, fn, this::getNextSyntheticId);
+  }
+
+  public ProgramMethod createMethod(
+      ProgramDefinition context,
+      DexItemFactory factory,
+      Consumer<SyntheticMethodBuilder> fn,
+      MethodProcessingId methodProcessingId) {
+    return createMethod(context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
+  }
+
+  private ProgramMethod createMethod(
+      ProgramDefinition context,
+      DexItemFactory factory,
+      Consumer<SyntheticMethodBuilder> fn,
+      Supplier<String> syntheticIdSupplier) {
+    assert nextSyntheticId != INVALID_ID_AFTER_SYNTHETIC_FINALIZATION;
     // Obtain the outer synthesizing context in the case the context itself is synthetic.
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = getSynthesizingContext(context);
-    DexType type = outerContext.createHygienicType(getNextSyntheticId(), factory);
+    DexType type = outerContext.createHygienicType(syntheticIdSupplier.get(), factory);
     SyntheticClassBuilder classBuilder = new SyntheticClassBuilder(type, outerContext, factory);
     DexProgramClass clazz = classBuilder.addMethod(fn).build();
     ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index ec7794d..8cddcfc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -41,17 +41,29 @@
   }
 
   @Override
-  HashCode computeHash() {
+  HashCode computeHash(boolean intermediate) {
     Hasher hasher = Hashing.sha256().newHasher();
+    if (intermediate) {
+      // If in intermediate mode, include the context type as sharing is restricted to within a
+      // single context.
+      hasher.putInt(getContext().getSynthesizingContextType().hashCode());
+    }
     method.getDefinition().hashSyntheticContent(hasher);
     return hasher.hash();
   }
 
   @Override
-  boolean isEquivalentTo(SyntheticDefinition other) {
+  boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate) {
     if (!(other instanceof SyntheticMethodDefinition)) {
       return false;
     }
+    if (intermediate
+        && getContext().getSynthesizingContextType()
+            != other.getContext().getSynthesizingContextType()) {
+      // If in intermediate mode, only synthetics within the same context should be considered
+      // equal.
+      return false;
+    }
     SyntheticMethodDefinition o = (SyntheticMethodDefinition) other;
     return method.getDefinition().isSyntheticContentEqual(o.method.getDefinition());
   }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java b/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
index db31dac..8fd9edb 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/MissingDefinitionsDiagnostic.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.tracereferences.Tracer.TracedClassImpl;
 import com.android.tools.r8.tracereferences.Tracer.TracedFieldImpl;
 import com.android.tools.r8.tracereferences.Tracer.TracedMethodImpl;
+import com.android.tools.r8.tracereferences.Tracer.TracedReferenceBase;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -40,18 +41,27 @@
     return Position.UNKNOWN;
   }
 
+  private <T extends TracedReferenceBase<?, ?>> void appendSorted(
+      StringBuilder builder, Set<T> missing) {
+    missing.stream()
+        .map(element -> element.getReference())
+        .map(Object::toString)
+        .sorted()
+        .forEach(item -> builder.append("  ").append(item).append(System.lineSeparator()));
+  }
+
   @Override
   public String getDiagnosticMessage() {
     StringBuilder builder = new StringBuilder("Tracereferences found ");
     List<String> components = new ArrayList<>();
     if (missingClasses.size() > 0) {
-      components.add("" + missingClasses.size() + " classes");
+      components.add("" + missingClasses.size() + " classe(s)");
     }
     if (missingFields.size() > 0) {
-      components.add("" + missingClasses.size() + " fields");
+      components.add("" + missingFields.size() + " field(s)");
     }
     if (missingMethods.size() > 0) {
-      components.add("" + missingClasses.size() + " methods");
+      components.add("" + missingMethods.size() + " method(s)");
     }
     assert components.size() > 0;
     for (int i = 0; i < components.size(); i++) {
@@ -63,27 +73,12 @@
     builder.append(" without definition");
     builder.append(System.lineSeparator());
     builder.append(System.lineSeparator());
-    builder.append("Classes without definition:");
-    missingClasses.forEach(
-        clazz ->
-            builder
-                .append("  ")
-                .append(clazz.getReference().toString())
-                .append(System.lineSeparator()));
-    builder.append("Fields without definition");
-    missingFields.forEach(
-        field ->
-            builder
-                .append("  ")
-                .append(field.getReference().toString())
-                .append(System.lineSeparator()));
-    builder.append("Methods without definition");
-    missingMethods.forEach(
-        method ->
-            builder
-                .append("  ")
-                .append(method.getReference().toString())
-                .append(System.lineSeparator()));
+    builder.append("Classe(s) without definition:" + System.lineSeparator());
+    appendSorted(builder, missingClasses);
+    builder.append("Field(s) without definition:" + System.lineSeparator());
+    appendSorted(builder, missingFields);
+    builder.append("Method(s) without definition:" + System.lineSeparator());
+    appendSorted(builder, missingMethods);
     return builder.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
index 0f9c27e..8c5cce7 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -16,12 +16,12 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Consumer;
 
 @Keep
 public class TraceReferences {
@@ -30,38 +30,43 @@
     ExceptionUtils.withCompilationHandler(command.getReporter(), () -> runInternal(command));
   }
 
+  private static void forEachDescriptor(ProgramResourceProvider provider, Consumer<String> consumer)
+      throws ResourceException, IOException {
+    for (ProgramResource programResource : provider.getProgramResources()) {
+      if (programResource.getKind() == Kind.DEX) {
+        assert programResource.getClassDescriptors() == null;
+        for (DexProgramClass clazz :
+            new ApplicationReader(
+                    AndroidApp.builder()
+                        .addDexProgramData(ImmutableList.of(programResource.getBytes()))
+                        .build(),
+                    new InternalOptions(),
+                    Timing.empty())
+                .read()
+                .classes()) {
+          consumer.accept(clazz.getType().toDescriptorString());
+        }
+      } else {
+        assert programResource.getClassDescriptors() != null;
+        programResource.getClassDescriptors().forEach(consumer);
+      }
+    }
+  }
+
   private static void runInternal(TraceReferencesCommand command)
       throws IOException, ResourceException {
     AndroidApp.Builder builder = AndroidApp.builder();
     command.getLibrary().forEach(builder::addLibraryResourceProvider);
     command.getTarget().forEach(builder::addLibraryResourceProvider);
     command.getSource().forEach(builder::addProgramResourceProvider);
-    Set<String> tagetDescriptors = new HashSet<>();
+    Set<String> targetDescriptors = new HashSet<>();
     command
         .getTarget()
-        .forEach(provider -> tagetDescriptors.addAll(provider.getClassDescriptors()));
+        .forEach(provider -> targetDescriptors.addAll(provider.getClassDescriptors()));
     for (ProgramResourceProvider provider : command.getSource()) {
-      for (ProgramResource programResource : provider.getProgramResources()) {
-        if (programResource.getKind() == Kind.DEX) {
-          command.getReporter().warning(new StringDiagnostic("DEX files not fully supported"));
-          assert programResource.getClassDescriptors() == null;
-          for (DexProgramClass clazz :
-              new ApplicationReader(
-                      AndroidApp.builder()
-                          .addDexProgramData(ImmutableList.of(programResource.getBytes()))
-                          .build(),
-                      new InternalOptions(),
-                      Timing.empty())
-                  .read()
-                  .classes()) {
-            tagetDescriptors.remove(clazz.getType().toDescriptorString());
-          }
-        } else {
-          tagetDescriptors.removeAll(programResource.getClassDescriptors());
-        }
-      }
+      forEachDescriptor(provider, targetDescriptors::remove);
     }
-    Tracer tracer = new Tracer(tagetDescriptors, builder.build(), command.getReporter());
+    Tracer tracer = new Tracer(targetDescriptors, builder.build(), command.getReporter());
     tracer.run(command.getConsumer());
   }
 
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index 6923654..ce8f565 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.tracereferences;
 
 import static com.android.tools.r8.utils.FileUtils.isArchive;
+import static com.android.tools.r8.utils.FileUtils.isDexFile;
 
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileResourceProvider;
@@ -11,7 +12,10 @@
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
@@ -27,6 +31,7 @@
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 @Keep
@@ -173,6 +178,16 @@
       }
       if (isArchive(file)) {
         traceSourceBuilder.add(ArchiveResourceProvider.fromArchive(file, false));
+      } else if (isDexFile(file)) {
+        traceSourceBuilder.add(
+            new ProgramResourceProvider() {
+              ProgramResource dexResource = ProgramResource.fromFile(Kind.DEX, file);
+
+              @Override
+              public Collection<ProgramResource> getProgramResources() throws ResourceException {
+                return Collections.singletonList(dexResource);
+              }
+            });
       } else {
         error(new StringDiagnostic("Unsupported source file type", new PathOrigin(file)));
       }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
index 679c330..5750fe1 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesConsumer.java
@@ -87,4 +87,56 @@
    * Tracing has finished. There will be no more calls to any of the <code>acceptXXX</code> methods.
    */
   default void finished() {}
+
+  static TraceReferencesConsumer emptyConsumer() {
+    return ForwardingConsumer.EMPTY_CONSUMER;
+  }
+
+  /** Forwarding consumer to delegate to an optional existing consumer. */
+  @Keep
+  class ForwardingConsumer implements TraceReferencesConsumer {
+
+    private static final TraceReferencesConsumer EMPTY_CONSUMER = new ForwardingConsumer(null);
+
+    private final TraceReferencesConsumer consumer;
+
+    public ForwardingConsumer(TraceReferencesConsumer consumer) {
+      this.consumer = consumer;
+    }
+
+    @Override
+    public void acceptType(TracedClass tracedClass) {
+      if (consumer != null) {
+        consumer.acceptType(tracedClass);
+      }
+    }
+
+    @Override
+    public void acceptField(TracedField tracedField) {
+      if (consumer != null) {
+        consumer.acceptField(tracedField);
+      }
+    }
+
+    @Override
+    public void acceptMethod(TracedMethod tracedMethod) {
+      if (consumer != null) {
+        consumer.acceptMethod(tracedMethod);
+      }
+    }
+
+    @Override
+    public void acceptPackage(PackageReference pkg) {
+      if (consumer != null) {
+        consumer.acceptPackage(pkg);
+      }
+    }
+
+    @Override
+    public void finished() {
+      if (consumer != null) {
+        consumer.finished();
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 110fb8b..72db701 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -369,6 +369,10 @@
 
     @Override
     public void registerInvokeVirtual(DexMethod method) {
+      if (method.holder.isArrayType()) {
+        addType(method.holder);
+        return;
+      }
       ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
       DexEncodedMethod target =
           resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
diff --git a/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java b/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
index a42fd3a..59cac42 100644
--- a/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ComparatorUtils.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.errors.Unreachable;
 import java.util.Comparator;
 import java.util.List;
 
@@ -61,4 +62,10 @@
       return diff;
     };
   }
+
+  public static <T> Comparator<T> unreachableComparator() {
+    return (t1, t2) -> {
+      throw new Unreachable();
+    };
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
index b7c7a87..3338dde 100644
--- a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
@@ -10,6 +10,10 @@
 
 public class FunctionUtils {
 
+  public static <S, T> T applyOrElse(S object, Function<S, T> fn, T orElse) {
+    return object != null ? fn.apply(object) : orElse;
+  }
+
   public static <S, T, R> Function<S, Function<T, R>> curry(BiFunction<S, T, R> function) {
     return arg -> arg2 -> function.apply(arg, arg2);
   }
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 3f5ceb6..dd85ad0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -183,8 +183,7 @@
     protoShrinking.enableGeneratedMessageLiteShrinking = true;
     protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true;
     protoShrinking.enableGeneratedExtensionRegistryShrinking = true;
-    // TODO(b/170798502): Reland enum unboxing for proto enums.
-    // protoShrinking.enableEnumLiteProtoShrinking = true;
+    protoShrinking.enableEnumLiteProtoShrinking = true;
   }
 
   void disableAllOptimizations() {
@@ -602,6 +601,7 @@
   private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
   private final KotlinOptimizationOptions kotlinOptimizationOptions =
       new KotlinOptimizationOptions();
+  private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
   public final TestingOptions testing = new TestingOptions();
 
   public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of();
@@ -629,6 +629,10 @@
     return kotlinOptimizationOptions;
   }
 
+  public DesugarSpecificOptions desugarSpecificOptions() {
+    return desugarSpecificOptions;
+  }
+
   public static boolean shouldEnableKeepRuleSynthesisForRecompilation() {
     return System.getProperty("com.android.tools.r8.keepRuleSynthesisForRecompilation") != null;
   }
@@ -1164,6 +1168,14 @@
         System.getProperty("com.android.tools.r8.disableKotlinSpecificOptimizations") != null;
   }
 
+  // Temporary desugar specific options to make progress on b/147485959
+  // All options should be including bugs to either fix the underlying issue or extend the api.
+  public static class DesugarSpecificOptions {
+    // b/172508621
+    public boolean sortMethodsOnCfOutput =
+        System.getProperty("com.android.tools.r8.sortMethodsOnCfWriting") != null;
+  }
+
   public static class CallSiteOptimizationOptions {
 
     // Each time we see an invoke with more dispatch targets than the threshold, we stop call site
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index f38c02a..b12de07 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -50,6 +50,16 @@
     };
   }
 
+  public static <T> T nextUntil(Iterator<T> iterator, Predicate<T> predicate) {
+    while (iterator.hasNext()) {
+      T element = iterator.next();
+      if (predicate.test(element)) {
+        return element;
+      }
+    }
+    return null;
+  }
+
   public static <T> T peekPrevious(ListIterator<T> iterator) {
     T previous = iterator.previous();
     T next = iterator.next();
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index f75d170..f33ed6f 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -177,4 +177,53 @@
     }
     return name.endsWith(CLASS_EXTENSION);
   }
+
+  public static class ZipBuilder {
+    private final Path zipFile;
+    private final ZipOutputStream stream;
+
+    private ZipBuilder(Path zipFile) throws IOException {
+      this.zipFile = zipFile;
+      stream = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)));
+    }
+
+    public static ZipBuilder builder(Path zipFile) throws IOException {
+      return new ZipBuilder(zipFile);
+    }
+
+    public ZipBuilder addFilesRelative(Path basePath, Collection<Path> filesToAdd)
+        throws IOException {
+      for (Path path : filesToAdd) {
+        ZipEntry zipEntry =
+            new ZipEntry(
+                StreamSupport.stream(
+                        Spliterators.spliteratorUnknownSize(
+                            basePath.relativize(path).iterator(), Spliterator.ORDERED),
+                        false)
+                    .map(Path::toString)
+                    .collect(Collectors.joining("/")));
+        stream.putNextEntry(zipEntry);
+        Files.copy(path, stream);
+        stream.closeEntry();
+      }
+      return this;
+    }
+
+    public ZipBuilder addFilesRelative(Path basePath, Path... filesToAdd) throws IOException {
+      return addFilesRelative(basePath, Arrays.asList(filesToAdd));
+    }
+
+    public ZipBuilder addBytes(String path, byte[] bytes) throws IOException {
+      ZipEntry zipEntry = new ZipEntry(path);
+      stream.putNextEntry(zipEntry);
+      stream.write(bytes);
+      stream.closeEntry();
+      return this;
+    }
+
+    public Path build() throws IOException {
+      stream.close();
+      return zipFile;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
index 6f6d1a5..d902126 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/SortedProgramMethodSet.java
@@ -9,17 +9,23 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.ComparatorUtils;
 import com.android.tools.r8.utils.ForEachable;
 import com.android.tools.r8.utils.ForEachableUtils;
 import java.util.Comparator;
 import java.util.Set;
+import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.function.Supplier;
 
 public class SortedProgramMethodSet extends ProgramMethodSet {
 
-  private SortedProgramMethodSet(Supplier<TreeMap<DexMethod, ProgramMethod>> backingFactory) {
+  private static final SortedProgramMethodSet EMPTY =
+      new SortedProgramMethodSet(() -> new TreeMap<>(ComparatorUtils.unreachableComparator()));
+
+  private SortedProgramMethodSet(Supplier<SortedMap<DexMethod, ProgramMethod>> backingFactory) {
     super(backingFactory);
   }
 
@@ -40,6 +46,14 @@
     return result;
   }
 
+  public static SortedProgramMethodSet createConcurrent() {
+    return new SortedProgramMethodSet(() -> new ConcurrentSkipListMap<>(DexMethod::slowCompareTo));
+  }
+
+  public static SortedProgramMethodSet empty() {
+    return EMPTY;
+  }
+
   @Override
   public SortedProgramMethodSet rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLens lens) {
diff --git a/src/test/java/com/android/tools/r8/NoUnusedInterfaceRemoval.java b/src/test/java/com/android/tools/r8/NoUnusedInterfaceRemoval.java
new file mode 100644
index 0000000..3cc8378
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NoUnusedInterfaceRemoval.java
@@ -0,0 +1,10 @@
+// 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NoUnusedInterfaceRemoval {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index de56e20..9167acd 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
 import com.android.tools.r8.shaking.NoStaticClassMergingRule;
+import com.android.tools.r8.shaking.NoUnusedInterfaceRemovalRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -60,6 +61,7 @@
   private boolean enableConstantArgumentAnnotations = false;
   private boolean enableInliningAnnotations = false;
   private boolean enableMemberValuePropagationAnnotations = false;
+  private boolean enableNoUnusedInterfaceRemovalAnnotations = false;
   private boolean enableNoVerticalClassMergingAnnotations = false;
   private boolean enableNoHorizontalClassMergingAnnotations = false;
   private boolean enableNoStaticClassMergingAnnotations = false;
@@ -83,6 +85,7 @@
     if (enableConstantArgumentAnnotations
         || enableInliningAnnotations
         || enableMemberValuePropagationAnnotations
+        || enableNoUnusedInterfaceRemovalAnnotations
         || enableNoVerticalClassMergingAnnotations
         || enableNoHorizontalClassMergingAnnotations
         || enableNoStaticClassMergingAnnotations
@@ -425,6 +428,15 @@
     addInternalKeepRules(sb.toString());
   }
 
+  public T enableNoUnusedInterfaceRemovalAnnotations() {
+    if (!enableNoUnusedInterfaceRemovalAnnotations) {
+      enableNoUnusedInterfaceRemovalAnnotations = true;
+      addInternalMatchInterfaceRule(
+          NoUnusedInterfaceRemovalRule.RULE_NAME, NoUnusedInterfaceRemoval.class);
+    }
+    return self();
+  }
+
   public T enableNoVerticalClassMergingAnnotations() {
     if (!enableNoVerticalClassMergingAnnotations) {
       enableNoVerticalClassMergingAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index ddcb6c3..e37e48e 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -267,6 +267,17 @@
     return self();
   }
 
+  public Path writeToDirectory() throws IOException {
+    Path directory = state.getNewTempFolder();
+    writeToDirectory(directory);
+    return directory;
+  }
+
+  public CR writeToDirectory(Path directory) throws IOException {
+    app.writeToDirectory(directory, getOutputMode());
+    return self();
+  }
+
   public CodeInspector inspector() throws IOException, ExecutionException {
     return new CodeInspector(app);
   }
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 20ea852..1ac00bb 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.TestRuntime.NoneRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -43,6 +44,10 @@
     return runtime.isCf();
   }
 
+  public boolean isCfRuntime(CfVm vm) {
+    return runtime.isCf() && runtime.asCf().getVm() == vm;
+  }
+
   public boolean isNoneRuntime() {
     return runtime == NoneRuntime.getInstance();
   }
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 85214af..849e7d7 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -13,7 +13,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
 import java.util.function.Function;
 import org.hamcrest.Matcher;
 
@@ -40,7 +39,7 @@
 
   public abstract RR disassemble() throws IOException, ExecutionException;
 
-  public RR apply(Consumer<RR> fn) {
+  public <E extends Throwable> RR apply(ThrowingConsumer<RR, E> fn) throws E {
     fn.accept(self());
     return self();
   }
@@ -66,6 +65,13 @@
     return assertSuccessWithOutputLines(Arrays.asList(expected));
   }
 
+  public RR assertSuccessWithOutputLinesIf(boolean condition, String... expected) {
+    if (condition) {
+      return assertSuccessWithOutputLines(Arrays.asList(expected));
+    }
+    return self();
+  }
+
   public RR assertSuccessWithOutputLines(List<String> expected) {
     return assertSuccessWithOutput(StringUtils.lines(expected));
   }
@@ -75,6 +81,13 @@
     return assertFailure();
   }
 
+  public RR assertFailureWithErrorThatMatchesIf(boolean condition, Matcher<String> matcher) {
+    if (condition) {
+      return assertFailureWithErrorThatMatches(matcher);
+    }
+    return self();
+  }
+
   public RR assertFailureWithOutput(String expected) {
     assertStdoutMatches(is(expected));
     return assertFailure();
@@ -83,4 +96,12 @@
   public RR assertFailureWithErrorThatThrows(Class<? extends Throwable> expectedError) {
     return assertFailureWithErrorThatMatches(containsString(expectedError.getName()));
   }
+
+  public RR assertFailureWithErrorThatThrowsIf(
+      boolean condition, Class<? extends Throwable> expectedError) {
+    if (condition) {
+      return assertFailureWithErrorThatThrows(expectedError);
+    }
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 11d410f..8624277 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -139,12 +139,15 @@
             "Base::foo()",
             "Base::foo1()",
             "Base::foo2()",
+            "Base::foo3()",
             "Sub1::foo1()",
             "Itf1::foo1(0) >> Sub1::foo1()",
             "Sub1::bar1(0)",
+            "Sub1::foo3()",
             "Sub2::foo2()",
             "Itf2::foo2(0) >> Sub2::foo2()",
-            "Sub2::bar2(0)");
+            "Sub2::bar2(0)",
+            "Sub2::foo3()");
     Class<?> mainClass = TestMain.class;
     if (parameters.isCfRuntime()) {
       // Only run JVM reference on CF runtimes.
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateNonOverridePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateNonOverridePublicizerTest.java
new file mode 100644
index 0000000..fa7e479
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateNonOverridePublicizerTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateNonOverridePublicizerTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean allowAccessModification;
+  private final String[] EXPECTED =
+      new String[] {"SubViewModel.clearNotOverriding()", "ViewModel.clear()"};
+
+  @Parameters(name = "{0}, allowAccessModification: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public PackagePrivateNonOverridePublicizerTest(
+      TestParameters parameters, boolean allowAccessModification) {
+    this.parameters = parameters;
+    this.allowAccessModification = allowAccessModification;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .applyIf(allowAccessModification, TestShrinkerBuilder::allowAccessModification)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(ViewModel.class);
+              // ViewModel.clear() is package private. When we publicize the method, we can inline
+              // the clearBridge() into Main and thereby remove the ViewModel class entirely.
+              if (allowAccessModification) {
+                assertThat(clazz, not(isPresent()));
+              } else {
+                assertThat(clazz, isPresent());
+              }
+            });
+  }
+
+  @NeverClassInline
+  @NoVerticalClassMerging
+  public static class SubViewModel extends ViewModel {
+
+    @NeverInline
+    public void clearNotOverriding() {
+      System.out.println("SubViewModel.clearNotOverriding()");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      SubViewModel subViewModel = new SubViewModel();
+      subViewModel.clearNotOverriding();
+      subViewModel.clearBridge();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
new file mode 100644
index 0000000..ccd0b21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.accessrelaxation;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+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.TestRunResult;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+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 PackagePrivateOverridePublicizerTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String[] EXPECTED = new String[] {"SubViewModel.clear()", "ViewModel.clear()"};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PackagePrivateOverridePublicizerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertSuccessOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .allowAccessModification()
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertSuccessOutput);
+  }
+
+  private void assertSuccessOutput(TestRunResult<?> result) {
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+      result.assertFailureWithErrorThatMatches(containsString("overrides final"));
+    } else {
+      result.assertSuccessWithOutputLines(EXPECTED);
+    }
+  }
+
+  @NeverClassInline
+  public static class SubViewModel extends ViewModel {
+
+    @NeverInline
+    public void clear() {
+      System.out.println("SubViewModel.clear()");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      SubViewModel subViewModel = new SubViewModel();
+      subViewModel.clear();
+      subViewModel.clearBridge();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
index 4f5becc..0c25396 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Base.java
@@ -38,10 +38,17 @@
     return foo2();
   }
 
+  @NeverPropagateValue
+  @NeverInline
+  private String foo3() {
+    return "Base::foo3()";
+  }
+
   @NeverInline
   public void dump() {
     System.out.println(foo());
     System.out.println(foo1());
     System.out.println(foo2());
+    System.out.println(foo3());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
index b45a708..deb2559 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub1.java
@@ -23,11 +23,17 @@
     return bar1(1);
   }
 
+  @NeverInline
+  private String foo3() {
+    return "Sub1::foo3()";
+  }
+
   @Override
   public void dump() {
     System.out.println(foo1());
     System.out.println(foo1(0));
     System.out.println(bar1(0));
+    System.out.println(foo3());
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
index fbc2ebc..15cc549 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/privateinstance/Sub2.java
@@ -32,11 +32,17 @@
     return bar2(2);
   }
 
+  @NeverInline
+  private String foo3() {
+    return "Sub2::foo3()";
+  }
+
   @Override
   public void dump() {
     System.out.println(foo2());
     System.out.println(foo2(0));
     System.out.println(bar2(0));
+    System.out.println(foo3());
     try {
       bar1(0);
     } catch (AssertionError e) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
new file mode 100644
index 0000000..3b957c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import org.junit.Test;
+
+public class PrivateAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+  public PrivateAndInterfaceMethodCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    // TODO(b/167981556): Should always succeed.
+    boolean expectedToSucceed =
+        !enableHorizontalClassMerging
+            || parameters.isCfRuntime(CfVm.JDK11)
+            || parameters.isDexRuntime();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLinesIf(expectedToSucceed, "A.foo()", "B.bar()", "J.foo()")
+        .assertFailureWithErrorThatThrowsIf(!expectedToSucceed, IllegalAccessError.class);
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+      new B().bar();
+      new C().foo();
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+
+    @NeverInline
+    default void foo() {
+      System.out.println("J.foo()");
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    @NeverInline
+    private void foo() {
+      System.out.println("A.foo()");
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class B {
+
+    // Only here to make sure that B is not made abstract as a result of tree shaking.
+    @NeverInline
+    public void bar() {
+      System.out.println("B.bar()");
+    }
+  }
+
+  @NeverClassInline
+  static class C extends B implements J {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java
new file mode 100644
index 0000000..df03d8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class PrivateAndStaticMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+  public PrivateAndStaticMethodCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo()", "A.bar()", "B.foo()", "B.bar()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+      new A().bar();
+      new B().foo();
+      new B().bar();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    @NeverInline
+    private static void foo() {
+      System.out.println("A.foo()");
+    }
+
+    @NeverInline
+    private void bar() {
+      System.out.println("A.bar()");
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    @NeverInline
+    private void foo() {
+      System.out.println("B.foo()");
+    }
+
+    @NeverInline
+    private static void bar() {
+      System.out.println("B.bar()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
new file mode 100644
index 0000000..356acd7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class StaticAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+  public StaticAndInterfaceMethodCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .addHorizontallyMergedClassesInspectorIf(
+            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A.foo()", "A.baz()", "B.bar()", "J.foo()");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      A.foo();
+      new A().baz();
+      new B().bar();
+      new C().foo();
+    }
+  }
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {
+
+    @NeverInline
+    default void foo() {
+      System.out.println("J.foo()");
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    @NeverInline
+    static void foo() {
+      System.out.println("A.foo()");
+    }
+
+    // Only here to make sure that A is not made abstract as a result of tree shaking.
+    @NeverInline
+    public void baz() {
+      System.out.println("A.baz()");
+    }
+  }
+
+  @NoVerticalClassMerging
+  static class B {
+
+    // Only here to make sure that B is not made abstract as a result of tree shaking.
+    @NeverInline
+    public void bar() {
+      System.out.println("B.bar()");
+    }
+  }
+
+  @NeverClassInline
+  static class C extends B implements J {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
new file mode 100644
index 0000000..52b506f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import org.junit.Test;
+
+public class StaticAndVirtualMethodCollisionTest extends HorizontalClassMergingTestBase {
+
+  public StaticAndVirtualMethodCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void test() throws Exception {
+    // TODO(b/172415620): Handle static/virtual method collisions.
+    assertFailsCompilationIf(
+        enableHorizontalClassMerging,
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .addOptionsModification(
+                    options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+                .addHorizontallyMergedClassesInspectorIf(
+                    enableHorizontalClassMerging,
+                    inspector -> inspector.assertMergedInto(B.class, A.class))
+                .enableInliningAnnotations()
+                .enableNeverClassInliningAnnotations()
+                .setMinApi(parameters.getApiLevel())
+                .run(parameters.getRuntime(), Main.class)
+                .assertSuccessWithOutputLines("A.foo()", "A.bar()", "B.foo()", "B.bar()"),
+        e -> assertThat(e.getCause().getMessage(), containsString("Duplicate method")));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().foo();
+      new A().bar();
+      new B().foo();
+      new B().bar();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("A.foo()");
+    }
+
+    @NeverInline
+    public void bar() {
+      System.out.println("A.bar()");
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("B.foo()");
+    }
+
+    @NeverInline
+    public static void bar() {
+      System.out.println("B.bar()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithGetNameTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithGetNameTest.java
new file mode 100644
index 0000000..df89c21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithGetNameTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.codeinspector.ClassSubject;
+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 VerticalClassMergingWithGetNameTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public VerticalClassMergingWithGetNameTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addVerticallyMergedClassesInspector(
+            inspector -> inspector.assertMergedIntoSubtype(A.class))
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .apply(
+            runResult -> {
+              ClassSubject bClassSubject = runResult.inspector().clazz(B.class);
+              assertThat(bClassSubject, isPresent());
+              runResult.assertSuccessWithOutputLines(
+                  bClassSubject.getFinalName(), bClassSubject.getFinalName());
+            });
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new B().print();
+    }
+  }
+
+  static class A {
+
+    @NeverInline
+    public void print() {
+      System.out.println(A.class.getName());
+    }
+  }
+
+  static class B extends A {
+
+    @NeverInline
+    public void print() {
+      super.print();
+      System.out.println(B.class.getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
index ea450e4..dbe0d64 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
@@ -3,26 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compatproguard;
 
-import com.android.tools.r8.CompatProguardCommandBuilder;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.R8CompatTestBuilder;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.nio.file.Path;
 import java.util.List;
 
 class CompatProguardSmaliTestBase extends SmaliTestBase {
-  CodeInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
-      throws Exception {
-    Path dexOutputDir = temp.newFolder().toPath();
-    R8Command.Builder commandBuilder =
-        new CompatProguardCommandBuilder(true)
-            .setOutput(dexOutputDir, OutputMode.DexIndexed)
-            .addProguardConfiguration(proguardConfigurations, Origin.unknown());
-    ToolHelper.getAppBuilder(commandBuilder).addDexProgramData(builder.compile(), Origin.unknown());
-    return new CodeInspector(ToolHelper.runR8(commandBuilder.build()));
+
+  CodeInspector runCompatProguard(SmaliBuilder builder, List<String> keepRules) throws Exception {
+    return runCompatProguard(builder, testBuilder -> testBuilder.addKeepRules(keepRules));
+  }
+
+  CodeInspector runCompatProguard(
+      SmaliBuilder builder, ThrowableConsumer<R8CompatTestBuilder> configuration) throws Exception {
+    return testForR8Compat(Backend.DEX)
+        .addProgramDexFileData(builder.compile())
+        .applyIf(configuration != null, configuration)
+        .compile()
+        .inspector();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 6aa8805..9976c33 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -12,11 +12,10 @@
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
 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 com.google.common.collect.ImmutableList;
-import java.util.List;
 import org.junit.Test;
 
 public class ForNameTest extends CompatProguardSmaliTestBase {
@@ -35,11 +34,17 @@
         "return-void");
     builder.addClass(BOO);
 
-    List<String> pgConfigs = ImmutableList.of(
-        keepMainProguardConfiguration(CLASS_NAME),
-        "-dontshrink",
-        "-dontoptimize");
-    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector =
+        runCompatProguard(
+            builder,
+            testBuilder ->
+                testBuilder
+                    .addKeepMainRule(CLASS_NAME)
+                    // Add main dex rule to disable Class.forName() optimization.
+                    .addMainDexRules("-keep class " + CLASS_NAME)
+                    .noOptimization()
+                    .noTreeShaking()
+                    .setMinApi(AndroidApiLevel.B));
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -65,12 +70,18 @@
         "return-void");
     builder.addClass(BOO);
 
-    List<String> pgConfigs = ImmutableList.of(
-        keepMainProguardConfiguration(CLASS_NAME),
-        "-dontshrink",
-        "-dontoptimize",
-        "-dontobfuscate");
-    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector =
+        runCompatProguard(
+            builder,
+            testBuilder ->
+                testBuilder
+                    .addKeepMainRule(CLASS_NAME)
+                    // Add main dex rule to disable Class.forName() optimization.
+                    .addMainDexRules("-keep class " + CLASS_NAME)
+                    .noOptimization()
+                    .noMinification()
+                    .noTreeShaking()
+                    .setMinApi(AndroidApiLevel.B));
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
index 9c006cb..5fbca11 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultInterfaceWithIdentifierNameString.java
@@ -60,7 +60,8 @@
     companionClassSubject
         .allMethods()
         .forEach(
-            method -> assertTrue(method.streamInstructions().anyMatch(this::isInvokeClassForName)));
+            method ->
+                assertTrue(method.streamInstructions().noneMatch(this::isInvokeClassForName)));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 4a2e645..45f8957 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -147,8 +147,9 @@
             .writeToZip();
     GenerateMainDexListRunResult mainDexListFromDex =
         traceMainDex(Collections.emptyList(), Collections.singleton(out));
-    // Compiling in intermediate will not share the synthetics so there is one per call site.
-    assertEquals(MAIN_DEX_LIST_CLASSES.size() + 6, mainDexListFromDex.getMainDexList().size());
+    // Compiling in intermediate will share the synthetics within the context types so there is one
+    // synthetic class per backport in User2: Character.compare and Integer.compare.
+    assertEquals(MAIN_DEX_LIST_CLASSES.size() + 2, mainDexListFromDex.getMainDexList().size());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
index 7a0c8bb..d2851c1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingTest.java
@@ -65,6 +65,8 @@
     public static void main(String[] args) {
       nonNullTest();
       nullTest();
+      arrayGetAndPhiTest();
+      argumentsAndPhiTest();
     }
 
     private static void nonNullTest() {
@@ -81,6 +83,21 @@
       System.out.println(true);
     }
 
+    private static void arrayGetAndPhiTest() {
+      MyEnum[] values = MyEnum.values();
+      System.out.println(arrayGetAndPhi(values, true));
+      System.out.println(values[1].ordinal());
+      System.out.println(arrayGetAndPhi(values, false));
+      System.out.println(values[0].ordinal());
+    }
+
+    private static void argumentsAndPhiTest() {
+      System.out.println(argumentsAndPhi(MyEnum.A, MyEnum.B, true));
+      System.out.println(MyEnum.B.ordinal());
+      System.out.println(argumentsAndPhi(MyEnum.A, MyEnum.B, false));
+      System.out.println(MyEnum.A.ordinal());
+    }
+
     @NeverInline
     static MyEnum switchOn(int i) {
       MyEnum returnValue;
@@ -112,5 +129,15 @@
       }
       return returnValue;
     }
+
+    @NeverInline
+    static int arrayGetAndPhi(MyEnum[] enums, boolean b) {
+      return (b ? enums[1] : enums[0]).ordinal();
+    }
+
+    @NeverInline
+    static int argumentsAndPhi(MyEnum e0, MyEnum e1, boolean b) {
+      return (b ? e1 : e0).ordinal();
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 357fa0e..a2fe521 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -209,7 +210,7 @@
     }
   }
 
-  static class MethodProcessorMock implements MethodProcessor {
+  static class MethodProcessorMock extends MethodProcessor {
 
     @Override
     public Phase getPhase() {
@@ -225,6 +226,11 @@
     public boolean isProcessedConcurrently(ProgramMethod method) {
       return false;
     }
+
+    @Override
+    public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+      throw new Unreachable();
+    }
   }
 
   static class OptimizationFeedbackMock extends OptimizationFeedbackIgnore {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
new file mode 100644
index 0000000..aa385fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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 java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CastToUninstantiatedClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // TODO(b/172194277): Add support for synthetics when generating CF.
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public CastToUninstantiatedClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertThat(inspector.clazz(Uninstantiated.class), isAbsent()))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Got null", "Caught ClassCastException");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testNull();
+      testNonNull();
+    }
+
+    @NeverInline
+    private static void testNull() {
+      Uninstantiated u = (Uninstantiated) get(null);
+      System.out.println("Got " + u);
+    }
+
+    @NeverInline
+    private static void testNonNull() {
+      try {
+        Uninstantiated u = (Uninstantiated) get(new Object());
+        System.out.println("Got " + u);
+      } catch (ClassCastException e) {
+        System.out.println("Caught ClassCastException");
+      }
+    }
+
+    @NeverInline
+    private static Object get(Object o) {
+      List<Object> list = new ArrayList<>();
+      list.add(o);
+      return list.get(0);
+    }
+  }
+
+  static class Uninstantiated {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
index f99639f..26b927e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/ForNameTest.java
@@ -161,7 +161,7 @@
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 2, 2);
+    test(result, 1, 3);
 
     // R8 release, minification.
     result =
@@ -173,6 +173,6 @@
             .setMinApi(parameters.getApiLevel())
             // We are not checking output because it can't be matched due to minification. Just run.
             .run(parameters.getRuntime(), MAIN);
-    test(result, 2, 2);
+    test(result, 1, 3);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
new file mode 100644
index 0000000..d8d7c85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.templates;
+
+public class CfUtilityMethodsForCodeOptimizationsTemplates {
+
+  public static void throwClassCastExceptionIfNotNull(Object o) {
+    if (o != null) {
+      throw new ClassCastException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
new file mode 100644
index 0000000..c35135e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.templates;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateCfUtilityMethodsForCodeOptimizations extends MethodGenerationBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GenerateCfUtilityMethodsForCodeOptimizations(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Override
+  protected DexType getGeneratedType() {
+    return factory.createType(
+        "Lcom/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations;");
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return ImmutableList.of(CfUtilityMethodsForCodeOptimizationsTemplates.class);
+  }
+
+  @Test
+  public void test() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+    sorted.sort(Comparator.comparing(Class::getTypeName));
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+    assertEquals(
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new GenerateCfUtilityMethodsForCodeOptimizations(
+            getTestParameters().withNoneRuntime().build().iterator().next())
+        .generateMethodsAndWriteThemToFile();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 3f7bb60..fa7b74e 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
@@ -61,6 +62,12 @@
     }
 
     @Override
+    public void replaceCurrentInstructionWithConstClass(
+        AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo) {
+      throw new Unimplemented();
+    }
+
+    @Override
     public void replaceCurrentInstructionWithConstInt(IRCode code, int value) {
       throw new Unimplemented();
     }
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index aebb0cf..a144a0e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -56,7 +56,7 @@
           long paramNullCheckCount = countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
           // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
           assertEquals(2, ifzCount);
-          assertEquals(allowAccessModification ? 0 : 5, paramNullCheckCount);
+          assertEquals(0, paramNullCheckCount);
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index ad8c526..8775bd7 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -115,19 +115,19 @@
 
   // Without -adaptclassstrings
   private static void test1_rule1(TestParameters parameters, CodeInspector inspector) {
-    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
+    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 0 : 0;
     test1_rules(inspector, expectedRenamedIdentifierInMain, 0, 0);
   }
 
   // With -adaptclassstrings *.*A
   private static void test1_rule2(TestParameters parameters, CodeInspector inspector) {
-    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 2 : 1;
+    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 0 : 0;
     test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
   }
 
   // With -adaptclassstrings (no filter)
   private static void test1_rule3(TestParameters parameters, CodeInspector inspector) {
-    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 3 : 2;
+    int expectedRenamedIdentifierInMain = parameters.isCfRuntime() ? 1 : 1;
     test1_rules(inspector, expectedRenamedIdentifierInMain, 1, 1);
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java b/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java
new file mode 100644
index 0000000..cd9d54c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/PackagePrivateOverrideSameMethodNameTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateOverrideSameMethodNameTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String[] EXPECTED = new String[] {"SubViewModel.clear()", "ViewModel.clear()"};
+  private final boolean minification;
+
+  @Parameters(name = "{0}, minification: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public PackagePrivateOverrideSameMethodNameTest(TestParameters parameters, boolean minification) {
+    this.parameters = parameters;
+    this.minification = minification;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeFalse(minification);
+    testForRuntime(parameters)
+        .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertSuccessOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(ViewModel.class, SubViewModel.class, Main.class)
+            .addKeepMainRule(Main.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .minification(minification)
+            .run(parameters.getRuntime(), Main.class)
+            .apply(this::assertSuccessOutput);
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+      runResult.inspectFailure(this::inspect);
+    } else {
+      runResult.inspect(this::inspect);
+    }
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject viewModel = inspector.clazz(ViewModel.class);
+    assertThat(viewModel, isPresentAndRenamed(minification));
+    ClassSubject subViewModel = inspector.clazz(SubViewModel.class);
+    assertThat(subViewModel, isPresentAndRenamed(minification));
+    MethodSubject viewModelClear = viewModel.uniqueMethodWithName("clear");
+    assertThat(viewModelClear, isPresentAndRenamed(minification));
+    MethodSubject subViewModelClear = subViewModel.uniqueMethodWithName("clear");
+    assertThat(subViewModelClear, isPresentAndRenamed(minification));
+    assertEquals(viewModelClear.getFinalName(), subViewModelClear.getFinalName());
+    if (!minification) {
+      assertEquals("clear", viewModelClear.getFinalName());
+    }
+  }
+
+  private void assertSuccessOutput(TestRunResult<?> result) {
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+      result.assertFailureWithErrorThatMatches(containsString("overrides final"));
+    } else {
+      result.assertSuccessWithOutputLines(EXPECTED);
+    }
+  }
+
+  @NeverClassInline
+  public static class SubViewModel extends ViewModel {
+
+    @NeverInline
+    public void clear() {
+      System.out.println("SubViewModel.clear()");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      SubViewModel subViewModel = new SubViewModel();
+      subViewModel.clear();
+      subViewModel.clearBridge();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
index 8a36090..e374f2a 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
@@ -4,22 +4,38 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class RepackageWithDexItemBasedConstStringTest extends RepackageTestBase {
 
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters()
+            .withAllRuntimes()
+            .withApiLevelsEndingAtExcluding(AndroidApiLevel.L)
+            .build());
+  }
+
   public RepackageWithDexItemBasedConstStringTest(
       String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
     super(flattenPackageHierarchyOrRepackageClasses, parameters);
@@ -30,6 +46,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
+        .addMainDexClassRules(TestClass.class)
         .apply(this::configureRepackaging)
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
index 2c5f612..0e8f6cd 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/package_a/ViewModel.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.resolution.virtualtargets.package_a;
 
+import com.android.tools.r8.NeverInline;
+
 public abstract class ViewModel {
 
+  @NeverInline
   final void clear() {
     System.out.println("ViewModel.clear()");
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
index 0a1914f..5a3efa6 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.shaking.clinit;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
@@ -45,10 +45,15 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    // Check that A.<clinit>() is removed.
-    ClassSubject aClassSubject = inspector.clazz(A.class);
-    assertThat(aClassSubject, isPresent());
-    assertThat(aClassSubject.clinit(), not(isPresent()));
+    if (parameters.isCfRuntime()) {
+      // Check that A.<clinit>() is removed.
+      ClassSubject aClassSubject = inspector.clazz(A.class);
+      assertThat(aClassSubject, isPresent());
+      assertThat(aClassSubject.clinit(), isAbsent());
+    } else {
+      // Check that A is removed.
+      assertThat(inspector.clazz(A.class), isAbsent());
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java b/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java
index 76e1403..edebe31 100644
--- a/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/definitelynull/TestClass.java
@@ -7,11 +7,15 @@
 
   // Reflectively allocate A such that its instantiation cannot be identified.
   public static A getInstance() throws ReflectiveOperationException {
-    return (A)
-        Class.forName(
-                TestClass.class.getPackage().getName() + (System.nanoTime() > 0 ? ".A" : ".B"))
-            .getConstructor()
-            .newInstance();
+    try {
+      return (A)
+          Class.forName(
+                  TestClass.class.getPackage().getName() + (System.nanoTime() > 0 ? ".A" : ".B"))
+              .getConstructor()
+              .newInstance();
+    } catch (ClassCastException e) {
+      return null;
+    }
   }
 
   public static void main(String[] args) throws ReflectiveOperationException {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 91cd90c..04e2c9f 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -67,7 +67,7 @@
     this.forceProguardCompatibility = forceProguardCompatibility;
   }
 
-  private void test(Class mainClass, Class mentionedClass) throws Exception {
+  private void test(Class<?> mainClass, Class<?> mentionedClass) throws Exception {
     CodeInspector inspector =
         testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
             .noMinification()
@@ -96,9 +96,9 @@
 
   private void runAnnotationsTest(boolean keepAnnotations) throws Exception {
     // Add application classes including the annotation class.
-    Class mainClass = TestMain.class;
-    Class mentionedClassWithAnnotations = TestMain.MentionedClassWithAnnotation.class;
-    Class annotationClass = TestAnnotation.class;
+    Class<?> mainClass = TestMain.class;
+    Class<?> mentionedClassWithAnnotations = TestMain.MentionedClassWithAnnotation.class;
+    Class<?> annotationClass = TestAnnotation.class;
 
     CodeInspector inspector =
         testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
@@ -173,13 +173,14 @@
     runDefaultConstructorTest(TestClassWithoutDefaultConstructor.class, false);
   }
 
-  public void testCheckCast(Class mainClass, Class instantiatedClass, boolean containsCheckCast)
-      throws Exception {
-    List<String> proguardConfig = ImmutableList.of(
-        "-keep class " + mainClass.getCanonicalName() + " {",
-        "  public static void main(java.lang.String[]);",
-        "}",
-        "-dontobfuscate");
+  public void testCheckCast(
+      Class<?> mainClass, Class<?> instantiatedClass, boolean containsCheckCast) throws Exception {
+    List<String> proguardConfig =
+        ImmutableList.of(
+            "-keep class " + mainClass.getTypeName() + " {",
+            "  public static void main(java.lang.String[]);",
+            "}",
+            "-dontobfuscate");
 
     CodeInspector inspector =
         testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
@@ -192,7 +193,6 @@
     assertTrue(inspector.clazz(mainClass).isPresent());
     ClassSubject clazz = inspector.clazz(instantiatedClass);
     assertEquals(containsCheckCast, clazz.isPresent());
-    assertEquals(containsCheckCast, clazz.isPresent());
     if (clazz.isPresent()) {
       assertEquals(forceProguardCompatibility && containsCheckCast, !clazz.isAbstract());
     }
@@ -212,15 +212,18 @@
 
   @Test
   public void checkCastTest() throws Exception {
-    testCheckCast(TestMainWithCheckCast.class, TestClassWithDefaultConstructor.class, true);
+    testCheckCast(
+        TestMainWithCheckCast.class,
+        TestClassWithDefaultConstructor.class,
+        forceProguardCompatibility || parameters.isCfRuntime());
     testCheckCast(TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false);
   }
 
   public void testClassForName(boolean allowObfuscation) throws Exception {
-    Class mainClass = TestMainWithClassForName.class;
-    Class forNameClass1 = TestClassWithDefaultConstructor.class;
-    Class forNameClass2 = TestClassWithoutDefaultConstructor.class;
-    List<Class> forNameClasses = ImmutableList.of(forNameClass1, forNameClass2);
+    Class<?> mainClass = TestMainWithClassForName.class;
+    Class<?> forNameClass1 = TestClassWithDefaultConstructor.class;
+    Class<?> forNameClass2 = TestClassWithoutDefaultConstructor.class;
+    List<Class<?>> forNameClasses = ImmutableList.of(forNameClass1, forNameClass2);
     ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
     proguardConfigurationBuilder.add(
         "-keep class " + mainClass.getCanonicalName() + " {",
@@ -259,7 +262,7 @@
       CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
       assertEquals(3, proguardedInspector.allClasses().size());
       assertTrue(proguardedInspector.clazz(mainClass).isPresent());
-      for (Class clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
+      for (Class<?> clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
         assertTrue(proguardedInspector.clazz(clazz).isPresent());
         assertEquals(allowObfuscation, proguardedInspector.clazz(clazz).isRenamed());
       }
@@ -273,8 +276,8 @@
   }
 
   public void testClassGetMembers(boolean allowObfuscation) throws Exception {
-    Class mainClass = TestMainWithGetMembers.class;
-    Class withMemberClass = TestClassWithMembers.class;
+    Class<?> mainClass = TestMainWithGetMembers.class;
+    Class<?> withMemberClass = TestClassWithMembers.class;
 
     ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
     proguardConfigurationBuilder.add(
@@ -338,8 +341,8 @@
   }
 
   public void testAtomicFieldUpdaters(boolean allowObfuscation) throws Exception {
-    Class mainClass = TestMainWithAtomicFieldUpdater.class;
-    Class withVolatileFields = TestClassWithVolatileFields.class;
+    Class<?> mainClass = TestMainWithAtomicFieldUpdater.class;
+    Class<?> withVolatileFields = TestClassWithVolatileFields.class;
 
     ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
     proguardConfigurationBuilder.add(
@@ -469,7 +472,7 @@
       assert parameters.getApiLevel().getLevel() >= AndroidApiLevel.O.getLevel();
     }
 
-    Class mainClass = TestClass.class;
+    Class<?> mainClass = TestClass.class;
     R8TestCompileResult compileResult =
         testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
             .addProgramFiles(ToolHelper.getClassFilesForTestPackage(mainClass.getPackage()))
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
index c92d7e7..abd2407 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesUnsortedLocalVariablesTableTest.java
@@ -87,7 +87,7 @@
   @Test
   public void test() throws Exception {
     String expectedOutput = StringUtils.lines("In Api.api1 6", "Result 6");
-    testForR8(parameters.getBackend())
+    testForR8Compat(parameters.getBackend())
         .addInnerClasses(KeepParameterNamesUnsortedLocalVariablesTableTest.class)
         .addProgramClassFileData(dumpApi())
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java
index 7d76052..0074a89 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesArrayTypesTest.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
@@ -71,22 +71,26 @@
   @Test
   public void arrayTypes() throws Throwable {
     Path dir = temp.newFolder().toPath();
-    Path targetJar = dir.resolve("target.jar");
-    Path sourceJar = dir.resolve("source.jar");
-    ZipUtils.zip(
-        targetJar,
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(Target.class),
-        ToolHelper.getClassFileForTestClass(TargetFieldType.class),
-        ToolHelper.getClassFileForTestClass(TargetArgType.class),
-        ToolHelper.getClassFileForTestClass(TargetReturnType.class),
-        ToolHelper.getClassFileForTestClass(TargetInstantiatedType.class),
-        ToolHelper.getClassFileForTestClass(TargetInstanceOfType.class),
-        ToolHelper.getClassFileForTestClass(TargetCheckCastType.class));
-    ZipUtils.zip(
-        sourceJar,
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(Source.class));
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Target.class),
+                ToolHelper.getClassFileForTestClass(TargetFieldType.class),
+                ToolHelper.getClassFileForTestClass(TargetArgType.class),
+                ToolHelper.getClassFileForTestClass(TargetReturnType.class),
+                ToolHelper.getClassFileForTestClass(TargetInstantiatedType.class),
+                ToolHelper.getClassFileForTestClass(TargetInstanceOfType.class),
+                ToolHelper.getClassFileForTestClass(TargetCheckCastType.class),
+                ToolHelper.getClassFileForTestClass(TargetArrayCloneType.class),
+                ToolHelper.getClassFileForTestClass(TargetArrayCloneType2.class))
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
     DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
     MissingReferencesConsumer consumer = new MissingReferencesConsumer();
 
@@ -99,7 +103,6 @@
             .build());
 
     assertEquals(
-        consumer.tracedTypes,
         ImmutableSet.of(
             Reference.classFromClass(Target.class),
             Reference.classFromClass(TargetFieldType.class),
@@ -107,7 +110,10 @@
             Reference.classFromClass(TargetReturnType.class),
             Reference.classFromClass(TargetInstantiatedType.class),
             Reference.classFromClass(TargetInstanceOfType.class),
-            Reference.classFromClass(TargetCheckCastType.class)));
+            Reference.classFromClass(TargetCheckCastType.class),
+            Reference.classFromClass(TargetArrayCloneType.class),
+            Reference.classFromClass(TargetArrayCloneType2.class)),
+        consumer.tracedTypes);
     assertTrue(consumer.acceptFieldCalled);
     assertTrue(consumer.acceptMethodCalled);
   }
@@ -124,6 +130,10 @@
 
   static class TargetCheckCastType {}
 
+  static class TargetArrayCloneType {}
+
+  static class TargetArrayCloneType2 {}
+
   static class Target {
     public static TargetFieldType[] field;
 
@@ -139,6 +149,8 @@
       Object x = new TargetInstantiatedType[] {};
       boolean y = null instanceof TargetInstanceOfType;
       Object z = (TargetCheckCastType) null;
+      Object c = ((TargetArrayCloneType[]) null).clone();
+      Object c2 = ((TargetArrayCloneType2[][][][]) null).clone();
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index 5ea9bd0..77c1e53 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -21,18 +21,15 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.google.common.collect.ImmutableList;
-import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
 import kotlin.text.Charsets;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -213,20 +210,22 @@
       Consumer<DiagnosticsChecker> diagnosticsCheckerConsumer)
       throws Throwable {
     Path dir = temp.newFolder().toPath();
-    Path targetJar = dir.resolve("target.jar");
-    Path sourceJar = dir.resolve("source.jar");
-    ZipUtils.zip(
-        targetJar,
-        ToolHelper.getClassPathForTests(),
-        targetClasses.stream()
-            .map(ToolHelper::getClassFileForTestClass)
-            .collect(Collectors.toList()));
-    ZipUtils.zip(
-        sourceJar,
-        ToolHelper.getClassPathForTests(),
-        sourceClasses.stream()
-            .map(ToolHelper::getClassFileForTestClass)
-            .collect(Collectors.toList()));
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                targetClasses.stream()
+                    .map(ToolHelper::getClassFileForTestClass)
+                    .collect(Collectors.toList()))
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                sourceClasses.stream()
+                    .map(ToolHelper::getClassFileForTestClass)
+                    .collect(Collectors.toList()))
+            .build();
     runAndCheckOutput(targetJar, sourceJar, format, expected, diagnosticsCheckerConsumer);
   }
 
@@ -344,17 +343,19 @@
   @Test
   public void testMissingReference_errorToWarning() throws Throwable {
     Path dir = temp.newFolder().toPath();
-    Path targetJar = dir.resolve("target.jar");
-    Path sourceJar = dir.resolve("source.jar");
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(OtherTarget.class))
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
     Path output = dir.resolve("output.txt");
-    ZipUtils.zip(
-        targetJar,
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(OtherTarget.class));
-    ZipUtils.zip(
-        sourceJar,
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(Source.class));
     DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
     TraceReferences.run(
         TraceReferencesCommand.parse(
@@ -382,16 +383,6 @@
     assertEquals(0, diagnosticsChecker.infos.size());
   }
 
-  public static void zip(Path zipFile, String path, byte[] data) throws IOException {
-    try (ZipOutputStream stream =
-        new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
-      ZipEntry zipEntry = new ZipEntry(path);
-      stream.putNextEntry(zipEntry);
-      stream.write(data);
-      stream.closeEntry();
-    }
-  }
-
   private void checkTargetPartlyMissing(DiagnosticsChecker diagnosticsChecker) {
     Field field;
     Method method;
@@ -411,13 +402,17 @@
   @Test
   public void testMissingDefinition_printUses() throws Throwable {
     Path dir = temp.newFolder().toPath();
-    Path targetJar = dir.resolve("target.jar");
-    Path sourceJar = dir.resolve("source.jar");
-    zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
-    ZipUtils.zip(
-        sourceJar,
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(Source.class));
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
     try {
       runAndCheckOutput(
           targetJar,
@@ -436,9 +431,17 @@
   @Test
   public void testMissingDefinition_keepRules() throws Throwable {
     Path dir = temp.newFolder().toPath();
-    Path targetJar = dir.resolve("target.jar");
-    Path sourceJar = dir.resolve("source.jar");
-    zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
     ZipUtils.zip(
         sourceJar,
         ToolHelper.getClassPathForTests(),
@@ -465,13 +468,17 @@
   @Test
   public void testMissingDefinition_keepRulesAllowObfuscation() throws Throwable {
     Path dir = temp.newFolder().toPath();
-    Path targetJar = dir.resolve("target.jar");
-    Path sourceJar = dir.resolve("source.jar");
-    zip(targetJar, DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved());
-    ZipUtils.zip(
-        sourceJar,
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(Source.class));
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
     try {
       runAndCheckOutput(
           targetJar,
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
new file mode 100644
index 0000000..b21d409
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest.java
@@ -0,0 +1,220 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.tracereferences;
+
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsChecker;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+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;
+
+@RunWith(Parameterized.class)
+public class TraceReferencesDiagnosticTest extends TestBase {
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public TraceReferencesDiagnosticTest(TestParameters parameters) {}
+
+  @Test
+  public void traceReferencesDiagnosticClassesFieldsAndMethods() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(Target.class),
+                transformer(Target.class)
+                    .removeFields(
+                        (access, name, descriptor, signature, value) ->
+                            name.equals("missingField1"))
+                    .removeFields(
+                        (access, name, descriptor, signature, value) ->
+                            name.equals("missingField2"))
+                    .removeMethods(
+                        (access, name, descriptor, signature, exceptions) ->
+                            name.equals("missingMethod"))
+                    .transform())
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
+
+    String prefix = "  Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          ImmutableList.of(
+              "Tracereferences found 3 classe(s), 2 field(s) and 4 method(s) without definition",
+              StringUtils.lines(
+                  "Classe(s) without definition:",
+                  prefix + "Target1;",
+                  prefix + "Target2;",
+                  prefix + "Target3;"),
+              StringUtils.lines(
+                  "Field(s) without definition:",
+                  prefix + "Target;missingField1:I",
+                  prefix + "Target;missingField2:I"),
+              StringUtils.lines(
+                  "Method(s) without definition:",
+                  prefix + "Target1;<init>()V",
+                  prefix + "Target2;<init>()V",
+                  prefix + "Target3;<init>()V",
+                  prefix + "Target;missingMethod()V")),
+          handler ->
+              TraceReferences.run(
+                  TraceReferencesCommand.builder(handler)
+                      .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                      .addSourceFiles(sourceJar)
+                      .addTargetFiles(targetJar)
+                      .setConsumer(TraceReferencesConsumer.emptyConsumer())
+                      .build()));
+      fail("Unexpected success");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void traceReferencesDiagnosticFieldsAndMethods() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Target1.class),
+                ToolHelper.getClassFileForTestClass(Target2.class),
+                ToolHelper.getClassFileForTestClass(Target3.class))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(Target.class),
+                transformer(Target.class)
+                    .removeFields(
+                        (access, name, descriptor, signature, value) ->
+                            name.equals("missingField1"))
+                    .removeFields(
+                        (access, name, descriptor, signature, value) ->
+                            name.equals("missingField2"))
+                    .removeMethods(
+                        (access, name, descriptor, signature, exceptions) ->
+                            name.equals("missingMethod"))
+                    .transform())
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
+
+    String prefix = "  Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          ImmutableList.of(
+              "Tracereferences found 2 field(s) and 1 method(s) without definition",
+              StringUtils.lines(
+                  "Field(s) without definition:",
+                  prefix + "Target;missingField1:I",
+                  prefix + "Target;missingField2:I"),
+              StringUtils.lines(
+                  "Method(s) without definition:", prefix + "Target;missingMethod()V")),
+          handler ->
+              TraceReferences.run(
+                  TraceReferencesCommand.builder(handler)
+                      .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                      .addSourceFiles(sourceJar)
+                      .addTargetFiles(targetJar)
+                      .setConsumer(TraceReferencesConsumer.emptyConsumer())
+                      .build()));
+      fail("Unexpected success");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void traceReferencesDiagnosticMethods() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar =
+        ZipBuilder.builder(dir.resolve("target.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Target1.class),
+                ToolHelper.getClassFileForTestClass(Target2.class),
+                ToolHelper.getClassFileForTestClass(Target3.class))
+            .addBytes(
+                DescriptorUtils.getPathFromJavaType(Target.class),
+                transformer(Target.class)
+                    .removeMethods(
+                        (access, name, descriptor, signature, exceptions) ->
+                            name.equals("missingMethod"))
+                    .transform())
+            .build();
+    Path sourceJar =
+        ZipBuilder.builder(dir.resolve("source.jar"))
+            .addFilesRelative(
+                ToolHelper.getClassPathForTests(),
+                ToolHelper.getClassFileForTestClass(Source.class))
+            .build();
+
+    String prefix = "  Lcom/android/tools/r8/tracereferences/TraceReferencesDiagnosticTest$";
+    try {
+      DiagnosticsChecker.checkErrorsContains(
+          ImmutableList.of(
+              "Tracereferences found 1 method(s) without definition",
+              StringUtils.lines(
+                  "Method(s) without definition:", prefix + "Target;missingMethod()V")),
+          handler ->
+              TraceReferences.run(
+                  TraceReferencesCommand.builder(handler)
+                      .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+                      .addSourceFiles(sourceJar)
+                      .addTargetFiles(targetJar)
+                      .setConsumer(TraceReferencesConsumer.emptyConsumer())
+                      .build()));
+      fail("Unexpected success");
+    } catch (CompilationFailedException e) {
+      // Expected.
+    }
+  }
+
+  static class Target1 {}
+
+  static class Target2 {}
+
+  static class Target3 {}
+
+  static class Target {
+    public static int missingField1;
+    public static int missingField2;
+
+    public static void missingMethod() {}
+  }
+
+  static class Source {
+    public static void source() {
+      new Target1();
+      new Target2();
+      new Target3();
+
+      Target.missingField1 = 1;
+      Target.missingField2 = 2;
+      Target.missingMethod();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java
index 8b59d47..1387c3f 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesMissingReferencesInDexTest.java
@@ -64,10 +64,7 @@
     }
   }
 
-  @Test
-  public void missingClassReferenced() throws Throwable {
-    Path sourceDex = testForD8(Backend.DEX).addProgramClasses(Source.class).compile().writeToZip();
-
+  private void missingClassReferenced(Path sourceDex) throws Throwable {
     DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
     MissingReferencesConsumer consumer = new MissingReferencesConsumer();
 
@@ -89,14 +86,22 @@
   }
 
   @Test
-  public void missingFieldAndMethodReferenced() throws Throwable {
-    Path sourceDex =
+  public void missingClassReferencedInDexArchive() throws Throwable {
+    missingClassReferenced(
+        testForD8(Backend.DEX).addProgramClasses(Source.class).compile().writeToZip());
+  }
+
+  @Test
+  public void missingClassReferencedInDexFile() throws Throwable {
+    missingClassReferenced(
         testForD8(Backend.DEX)
             .addProgramClasses(Source.class)
-            .addProgramClassFileData(getClassWithTargetRemoved())
             .compile()
-            .writeToZip();
+            .writeToDirectory()
+            .resolve("classes.dex"));
+  }
 
+  private void missingFieldAndMethodReferenced(Path sourceDex) throws Throwable {
     DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
     MissingReferencesConsumer consumer = new MissingReferencesConsumer();
 
@@ -117,6 +122,27 @@
     assertTrue(consumer.acceptMethodCalled);
   }
 
+  @Test
+  public void missingFieldAndMethodReferencedInDexArchive() throws Throwable {
+    missingFieldAndMethodReferenced(
+        testForD8(Backend.DEX)
+            .addProgramClasses(Source.class)
+            .addProgramClassFileData(getClassWithTargetRemoved())
+            .compile()
+            .writeToZip());
+  }
+
+  @Test
+  public void missingFieldAndMethodReferencedInDexFile() throws Throwable {
+    missingFieldAndMethodReferenced(
+        testForD8(Backend.DEX)
+            .addProgramClasses(Source.class)
+            .addProgramClassFileData(getClassWithTargetRemoved())
+            .compile()
+            .writeToDirectory()
+            .resolve("classes.dex"));
+  }
+
   private byte[] getClassWithTargetRemoved() throws IOException {
     return transformer(Target.class)
         .removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("target"))
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
new file mode 100644
index 0000000..c113b0c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.utils.ThrowingAction;
+import java.util.function.Consumer;
+
+public class AssertUtils {
+
+  public static <E extends Throwable> void assertFailsCompilationIf(
+      boolean condition, ThrowingAction<E> action) throws E {
+    assertFailsCompilationIf(condition, action, null);
+  }
+
+  public static <E extends Throwable> void assertFailsCompilationIf(
+      boolean condition, ThrowingAction<E> action, Consumer<Throwable> consumer) throws E {
+    assertThrowsIf(condition, CompilationFailedException.class, action, consumer);
+  }
+
+  public static <E extends Throwable> void assertThrowsIf(
+      boolean condition, Class<? extends Throwable> clazz, ThrowingAction<E> action) throws E {
+    assertThrowsIf(condition, clazz, action, null);
+  }
+
+  public static <E extends Throwable> void assertThrowsIf(
+      boolean condition,
+      Class<? extends Throwable> clazz,
+      ThrowingAction<E> action,
+      Consumer<Throwable> consumer)
+      throws E {
+    if (condition) {
+      try {
+        action.execute();
+        fail("Expected action to fail with an exception, but succeeded");
+      } catch (Throwable e) {
+        assertEquals(clazz, e.getClass());
+        if (consumer != null) {
+          consumer.accept(e);
+        }
+      }
+    } else {
+      action.execute();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 29bebe1..c2a8cc8 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -100,6 +100,10 @@
     };
   }
 
+  public static Matcher<Subject> isAbsent() {
+    return not(isPresent());
+  }
+
   public static Matcher<Subject> isPresent() {
     return new TypeSafeMatcher<Subject>() {
       @Override
diff --git a/third_party/opensource-apps/android-suite-calculator.tar.gz.sha1 b/third_party/opensource-apps/android-suite-calculator.tar.gz.sha1
new file mode 100644
index 0000000..175f01a
--- /dev/null
+++ b/third_party/opensource-apps/android-suite-calculator.tar.gz.sha1
@@ -0,0 +1 @@
+51a99d63f0449fbe61d077ff5edbec397dd06a52
\ No newline at end of file
diff --git a/third_party/opensource-apps/anexplorer.tar.gz.sha1 b/third_party/opensource-apps/anexplorer.tar.gz.sha1
new file mode 100644
index 0000000..8ea778f
--- /dev/null
+++ b/third_party/opensource-apps/anexplorer.tar.gz.sha1
@@ -0,0 +1 @@
+e535845be72fed0ecf4ff622ccd7feeb59deea9f
\ No newline at end of file
diff --git a/third_party/opensource-apps/antennapod.tar.gz.sha1 b/third_party/opensource-apps/antennapod.tar.gz.sha1
new file mode 100644
index 0000000..fdb23b0
--- /dev/null
+++ b/third_party/opensource-apps/antennapod.tar.gz.sha1
@@ -0,0 +1 @@
+6819cb7dff48bfe09837a06aa2e9fd9fe06bcff5
\ No newline at end of file
diff --git a/tools/adb.py b/tools/adb.py
new file mode 100644
index 0000000..c6cece7
--- /dev/null
+++ b/tools/adb.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import subprocess
+import time
+import utils
+
+def install_apk_on_emulator(apk, emulator_id, quiet=False):
+  cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk]
+  if quiet:
+    subprocess.check_output(cmd)
+  else:
+    subprocess.check_call(cmd)
+
+
+def uninstall_apk_on_emulator(app_id, emulator_id):
+  process = subprocess.Popen(
+    ['adb', '-s', emulator_id, 'uninstall', app_id],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  stdout, stderr = process.communicate()
+
+  if stdout.strip() == 'Success':
+    # Successfully uninstalled
+    return
+
+  if 'Unknown package: {}'.format(app_id) in stderr:
+    # Application not installed
+    return
+
+  # Check if the app is listed in packages
+  packages = subprocess.check_output(['adb', 'shell', 'pm', 'list', 'packages'])
+  if not 'package:' + app_id in packages:
+    return
+
+  raise Exception(
+    'Unexpected result from `adb uninstall {}\nStdout: {}\nStderr: {}'.format(
+      app_id, stdout, stderr))
+
+
+def wait_for_emulator(emulator_id):
+  stdout = subprocess.check_output(['adb', 'devices'])
+  if '{}\tdevice'.format(emulator_id) in stdout:
+    return True
+
+  print('Emulator \'{}\' not connected; waiting for connection'.format(
+    emulator_id))
+
+  time_waited = 0
+  while True:
+    time.sleep(10)
+    time_waited += 10
+    stdout = subprocess.check_output(['adb', 'devices'])
+    if '{}\tdevice'.format(emulator_id) not in stdout:
+      print('... still waiting for connection')
+      if time_waited >= 5 * 60:
+        return False
+    else:
+      return True
+
+
+def run_monkey(app_id, emulator_id, apk, monkey_events, quiet, enable_logging):
+  if not wait_for_emulator(emulator_id):
+    return False
+
+  install_apk_on_emulator(apk, emulator_id, quiet)
+
+  # Intentionally using a constant seed such that the monkey generates the same
+  # event sequence for each shrinker.
+  random_seed = 42
+
+  cmd = ['adb', '-s', emulator_id, 'shell', 'monkey', '-p', app_id,
+         '-s', str(random_seed), str(monkey_events)]
+
+  try:
+    stdout = utils.RunCmd(cmd, quiet=quiet, logging=enable_logging)
+    succeeded = ('Events injected: {}'.format(monkey_events) in stdout)
+  except subprocess.CalledProcessError as e:
+    succeeded = False
+
+  uninstall_apk_on_emulator(app_id, emulator_id)
+
+  return succeeded
+
+
+def run_instrumented(app_id, test_id, emulator_id, apk, test_apk, quiet,
+                     enable_logging,
+                     test_runner='androidx.test.runner.AndroidJUnitRunner'):
+  if not wait_for_emulator(emulator_id):
+    return None
+
+  install_apk_on_emulator(apk, emulator_id, quiet)
+  install_apk_on_emulator(test_apk, emulator_id, quiet)
+
+  cmd = ['adb', '-s', emulator_id, 'shell', 'am', 'instrument', '-w',
+         '{}/{}'.format(test_id, test_runner)]
+
+  try:
+    stdout = utils.RunCmd(cmd, quiet=quiet, logging=enable_logging)
+    # The runner will print OK (X tests) if completed succesfully
+    succeeded = any("OK (" in s for s in stdout)
+  except subprocess.CalledProcessError as e:
+    succeeded = False
+
+  uninstall_apk_on_emulator(test_id, emulator_id)
+  uninstall_apk_on_emulator(app_id, emulator_id)
+
+  return succeeded
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 6d7359b..48c596b 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -281,6 +281,8 @@
     if dump.desugared_library_json():
       cmd.extend(['--desugared-lib', dump.desugared_library_json()])
     if compiler != 'd8' and dump.config_file():
+      if hasattr(args, 'config_file_consumer') and args.config_file_consumer:
+        args.config_file_consumer(dump.config_file())
       cmd.extend(['--pg-conf', dump.config_file()])
     if compiler != 'd8':
       cmd.extend(['--pg-map-output', '%s.map' % out])
diff --git a/tools/r8_release.py b/tools/r8_release.py
index cc4bebd..3e743b3 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -10,7 +10,7 @@
 import subprocess
 import sys
 import urllib
-import xml
+import xml.etree.ElementTree
 import zipfile
 
 import utils
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 04ca397..af6d337 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -1,8 +1,10 @@
 #!/usr/bin/env python
-# Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
+#  Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+#  for details. All rights reserved. Use of this source code is governed by a
+#  BSD-style license that can be found in the LICENSE file.
 
+import adb
+import apk_masseur
 import compiledump
 import gradle
 import jdk
@@ -22,7 +24,6 @@
   def __getattr__(self, name):
     return self.get(name, None)
 
-
 # To generate the files for a new app, navigate to the app source folder and
 # run:
 # ./gradlew clean :app:assembleRelease -Dcom.android.tools.r8.dumpinputtodirectory=<path>
@@ -38,6 +39,8 @@
       'id': None,
       'name': None,
       'dump_app': None,
+      'apk_app': None,
+      'apk_test': None,
       'skip': False,
       'url': None,  # url is not used but nice to have for updating apps
       'revision': None,
@@ -51,21 +54,80 @@
 
 APPS = [
   App({
+    'id': 'com.numix.calculator',
+    'name': 'Calculator',
+    'dump_app': 'dump_app.zip',
+    'apk_app': 'app-release.apk',
+    # Compiling tests fail: Library class android.content.res.XmlResourceParser
+    # implements program class org.xmlpull.v1.XmlPullParser. Nothing to really
+    # do about that.
+    'id_test': 'com.numix.calculator.test',
+    'dump_test': 'dump_test.zip',
+    'apk_test': 'app-release-androidTest.apk',
+    'url': 'https://github.com/numixproject/android-suite/tree/master/Calculator',
+    'revision': 'f58e1b53f7278c9b675d5855842c6d8a44cccb1f',
+    'folder': 'android-suite-calculator',
+  }),
+  App({
+    'id': 'dev.dworks.apps.anexplorer.pro',
+    'name': 'AnExplorer',
+    'dump_app': 'dump_app.zip',
+    'apk_app': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
+    'url': 'https://github.com/christofferqa/AnExplorer',
+    'revision': '365927477b8eab4052a1882d5e358057ae3dee4d',
+    'folder': 'anexplorer',
+  }),
+  App({
+    'id': 'de.danoeh.antennapod',
+    'name': 'AntennaPod',
+    'dump_app': 'dump_app.zip',
+    'apk_app': 'app-free-release.apk',
+    # TODO(b/172452102): Tests and monkey do not work
+    'id_test': 'de.danoeh.antennapod.test',
+    'dump_test': 'dump_test.zip',
+    'apk_test': 'app-free-release-androidTest.apk',
+    'url': 'https://github.com/christofferqa/AntennaPod.git',
+    'revision': '77e94f4783a16abe9cc5b78dc2d2b2b1867d8c06',
+    'folder': 'antennapod',
+    # TODO(b/172450929): Fix recompilation
+    'skip_recompilation': True
+  }),
+  App({
     'id': 'com.example.applymapping',
     'name': 'applymapping',
     'dump_app': 'dump_app.zip',
+    'apk_app': 'app-release.apk',
+    'id_test': 'com.example.applymapping.test',
+    'dump_test': 'dump_test.zip',
+    'apk_test': 'app-release-androidTest.apk',
     'url': 'https://github.com/mkj-gram/applymapping',
     'revision': 'e3ae14b8c16fa4718e5dea8f7ad00937701b3c48',
     'folder': 'applymapping',
-    'skip_recompilation': True,
   })
 ]
 
 
+def remove_print_lines(file):
+  with open(file) as f:
+    lines = f.readlines()
+  with open(file, 'w') as f:
+    for line in lines:
+      if '-printconfiguration' not in line:
+        f.write(line)
+
+
 def download_app(app_sha):
   utils.DownloadFromGoogleCloudStorage(app_sha)
 
 
+def is_logging_enabled_for(app, options):
+  if options.no_logging:
+    return False
+  if options.app_logging_filter and app.name not in options.app_logging_filter:
+    return False
+  return True
+
+
 def is_minified_r8(shrinker):
   return '-nolib' not in shrinker
 
@@ -87,6 +149,10 @@
   return os.path.join(app_dir, app.dump_app)
 
 
+def dump_test_for_app(app_dir, app):
+  return os.path.join(app_dir, app.dump_test)
+
+
 def get_results_for_app(app, options, temp_dir):
   app_folder = app.folder if app.folder else app.name + "_" + app.revision
   app_dir = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app_folder)
@@ -146,11 +212,11 @@
   for compilation_step in range(0, compilation_steps):
     if status != 'success':
       break
-    print('Compiling {} of {}'.format(compilation_step, compilation_steps))
+    print('Compiling {} of {}'.format(compilation_step + 1, compilation_steps))
     result = {}
     try:
       start = time.time()
-      (app_jar, new_recomp_jar) = \
+      (app_jar, mapping, new_recomp_jar) = \
         build_app_with_shrinker(
           app, options, temp_dir, app_dir, shrinker, compilation_step,
           compilation_steps, recomp_jar)
@@ -159,13 +225,13 @@
       result['build_status'] = 'success'
       result['recompilation_status'] = 'success'
       result['output_jar'] = app_jar
+      result['output_mapping'] = mapping
       result['dex_size'] = dex_size
       result['duration'] = int((end - start) * 1000)  # Wall time
       if (new_recomp_jar is None
           and not is_last_build(compilation_step, compilation_steps)):
         result['recompilation_status'] = 'failed'
         warn('Failed to build {} with {}'.format(app.name, shrinker))
-        results.append(result)
         break
       recomp_jar = new_recomp_jar
     except Exception as e:
@@ -175,7 +241,51 @@
       result['build_status'] = 'failed'
       status = 'failed'
 
+    original_app_apk = os.path.join(app_dir, app.apk_app)
+    app_apk_destination = os.path.join(
+      temp_dir,"{}_{}.apk".format(app.id, compilation_step))
+
+    if result.get('build_status') == 'success' and options.monkey:
+      # Make a copy of the given APK, move the newly generated dex files into the
+      # copied APK, and then sign the APK.
+      apk_masseur.masseur(
+        original_app_apk, dex=app_jar, resources='META-INF/services/*',
+        out=app_apk_destination,
+        quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+        keystore=options.keystore)
+
+      result['monkey_status'] = 'success' if adb.run_monkey(
+        app.id, options.emulator_id, app_apk_destination, options.monkey_events,
+        options.quiet, is_logging_enabled_for(app, options)) else 'failed'
+
+    if result.get('build_status') == 'success' and options.run_tests:
+      if not os.path.isfile(app_apk_destination):
+        apk_masseur.masseur(
+          original_app_apk, dex=app_jar, resources='META-INF/services/*',
+          out=app_apk_destination,
+          quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+          keystore=options.keystore)
+
+      # Compile the tests with the mapping file.
+      test_jar = build_test_with_shrinker(
+        app, options, temp_dir, app_dir,shrinker, compilation_step,
+        result['output_mapping'])
+      original_test_apk = os.path.join(app_dir, app.apk_test)
+      test_apk_destination = os.path.join(
+        temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
+      apk_masseur.masseur(
+        original_test_apk, dex=test_jar, resources='META-INF/services/*',
+        out=test_apk_destination,
+        quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+        keystore=options.keystore)
+      result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
+        app.id, app.id_test, options.emulator_id, app_apk_destination,
+        test_apk_destination, options.quiet,
+        is_logging_enabled_for(app, options)) else 'failed'
+
     results.append(result)
+    if result.get('recompilation_status') != 'success':
+      break
 
 
 def build_app_with_shrinker(app, options, temp_dir, app_dir, shrinker,
@@ -192,18 +302,25 @@
     'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
     'debug_agent': options.debug_agent,
     'program_jar': prev_recomp_jar,
-    'nolib': not is_minified_r8(shrinker)
+    'nolib': not is_minified_r8(shrinker),
+    'config_file_consumer': remove_print_lines,
   })
 
-  out_jar = os.path.join(temp_dir, "out.jar")
   compile_result = compiledump.run1(temp_dir, args, [])
+
+  out_jar = os.path.join(temp_dir, "out.jar")
+  out_mapping = os.path.join(temp_dir, "out.jar.map")
   app_jar = os.path.join(
     temp_dir, '{}_{}_{}_dex_out.jar'.format(
       app.name, shrinker, compilation_step_index))
+  app_mapping = os.path.join(
+    temp_dir, '{}_{}_{}_dex_out.jar.map'.format(
+      app.name, shrinker, compilation_step_index))
 
   if compile_result != 0 or not os.path.isfile(out_jar):
     assert False, "Compilation of app_jar failed"
   shutil.move(out_jar, app_jar)
+  shutil.move(out_mapping, app_mapping)
 
   recomp_jar = None
   if compilation_step_index < compilation_steps - 1:
@@ -216,7 +333,48 @@
           app.name, shrinker, compilation_step_index))
       shutil.move(out_jar, recomp_jar)
 
-  return (app_jar, recomp_jar)
+  return (app_jar, app_mapping, recomp_jar)
+
+def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker,
+                             compilation_step_index, mapping):
+  r8jar = os.path.join(
+    temp_dir, 'r8lib.jar' if is_minified_r8(shrinker) else 'r8.jar')
+
+  def rewrite_file(file):
+    remove_print_lines(file)
+    with open(file) as f:
+      lines = f.readlines()
+    with open(file, 'w') as f:
+      for line in lines:
+        if '-applymapping' not in line:
+          f.write(line + '\n')
+      f.write("-applymapping " + mapping + '\n')
+
+  args = AttrDict({
+    'dump': dump_test_for_app(app_dir, app),
+    'r8_jar': r8jar,
+    'ea': False if options.disable_assertions else True,
+    'version': 'master',
+    'compiler': 'r8full' if is_full_r8(shrinker) else 'r8',
+    'debug_agent': options.debug_agent,
+    'nolib': not is_minified_r8(shrinker),
+    # The config file will have an -applymapping reference to an old map.
+    # Update it to point to mapping file build in the compilation of the app.
+    'config_file_consumer': rewrite_file
+  })
+
+  compile_result = compiledump.run1(temp_dir, args, [])
+
+  out_jar = os.path.join(temp_dir, "out.jar")
+  test_jar = os.path.join(
+    temp_dir, '{}_{}_{}_test_out.jar'.format(
+      app.name, shrinker, compilation_step_index))
+
+  if compile_result != 0 or not os.path.isfile(out_jar):
+    assert False, "Compilation of test_jar failed"
+  shutil.move(out_jar, test_jar)
+
+  return test_jar
 
 
 def log_results_for_apps(result_per_shrinker_per_app, options):
@@ -288,6 +446,21 @@
       else:
         print(msg)
 
+      if options.monkey:
+        monkey_status = result.get('monkey_status')
+        if monkey_status != 'success':
+          app_error = True
+          warn('    monkey: {}'.format(monkey_status))
+        else:
+          success('    monkey: {}'.format(monkey_status))
+
+      if options.run_tests and 'instrumentation_test_status' in result:
+        test_status = result.get('instrumentation_test_status')
+        if test_status != 'success':
+          warn('    instrumentation_tests: {}'.format(test_status))
+        else:
+          success('    instrumentation_tests: {}'.format(test_status))
+
       recompilation_status = result.get('recompilation_status', '')
       if recompilation_status == 'failed':
         app_error = True
@@ -319,6 +492,9 @@
                     help='Disable assertions when compiling',
                     default=False,
                     action='store_true')
+  result.add_option('--emulator-id', '--emulator_id',
+                    help='Id of the emulator to use',
+                    default='emulator-5554')
   result.add_option('--golem',
                     help='Running on golem, do not download',
                     default=False,
@@ -421,7 +597,6 @@
     options.quiet = True
     options.no_logging = True
 
-
   with utils.TempDir() as temp_dir:
     if options.hash:
       # Download r8-<hash>.jar from