Merge commit '67a063cc' into dev-release
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index e7d8ab2..04251a6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -46,6 +46,10 @@
     return THE_EMPTY_ANNOTATIONS_SET;
   }
 
+  public int size() {
+    return annotations.length;
+  }
+
   @Override
   public int computeHashCode() {
     return Arrays.hashCode(annotations);
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 253d014..dfd31fb 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -33,6 +33,7 @@
     return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
   }
 
+  // TODO(b/140204899): Leverage refined receiver type if available.
   default Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
     assert isValidVirtualTarget(appInfo.app().options);
     // First add the target for receiver type method.type.
@@ -41,6 +42,8 @@
     // Add all matching targets from the subclass hierarchy.
     DexEncodedMethod encodedMethod = asResultOfResolve();
     DexMethod method = encodedMethod.method;
+    // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
+    //   receiver type if available.
     for (DexType type : appInfo.subtypes(method.holder)) {
       DexClass clazz = appInfo.definitionFor(type);
       if (!clazz.isInterface()) {
@@ -56,6 +59,7 @@
     return result;
   }
 
+  // TODO(b/140204899): Leverage refined receiver type if available.
   default Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
     assert isValidVirtualTarget(appInfo.app().options);
     Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
@@ -109,8 +113,9 @@
           }
         };
 
-    Set<DexType> set = appInfo.subtypes(method.holder);
-    for (DexType type : set) {
+    // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
+    //   receiver type if available.
+    for (DexType type : appInfo.subtypes(method.holder)) {
       DexClass clazz = appInfo.definitionFor(type);
       if (clazz.isInterface()) {
         ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index ab51239..7cbf14d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeDirectRange;
 import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -28,8 +27,6 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -148,13 +145,6 @@
   }
 
   @Override
-  public Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
-    DexEncodedMethod target = appView.appInfo().lookupDirectTarget(getInvokedMethod());
-    return target == null ? Collections.emptyList() : Collections.singletonList(target);
-  }
-
-  @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeDirect(getInvokedMethod(), invocationContext);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index ca84fba..860080f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeInterfaceRange;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -19,10 +18,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.stream.Collectors;
 
 public class InvokeInterface extends InvokeMethodWithReceiver {
 
@@ -104,34 +100,6 @@
   }
 
   @Override
-  public Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
-    // Leverage exact receiver type if available.
-    DexEncodedMethod singleTarget = lookupSingleTarget(appView, invocationContext);
-    if (singleTarget != null) {
-      return Collections.singletonList(singleTarget);
-    }
-    DexMethod method = getInvokedMethod();
-    Collection<DexEncodedMethod> targets =
-        appView
-            .appInfo()
-            .resolveMethodOnInterface(method.holder, method)
-            .lookupInterfaceTargets(appView.appInfo());
-    if (targets == null) {
-      return targets;
-    }
-    DexType staticReceiverType = getInvokedMethod().holder;
-    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView.withLiveness(), this);
-    // Leverage refined receiver type if available.
-    if (refinedReceiverType != staticReceiverType) {
-      return targets.stream()
-          .filter(m -> appView.isSubtype(m.method.holder, refinedReceiverType).isPossiblyTrue())
-          .collect(Collectors.toSet());
-    }
-    return targets;
-  }
-
-  @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeInterface(getInvokedMethod(), invocationContext);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index b790829..a974af6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -13,13 +13,17 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 public abstract class InvokeMethod extends Invoke {
 
@@ -61,11 +65,56 @@
 
   // In subclasses, e.g., invoke-virtual or invoke-super, use a narrower receiver type by using
   // receiver type and calling context---the holder of the method where the current invocation is.
+  // TODO(b/140204899): Refactor lookup methods to be defined in a single place.
   public abstract DexEncodedMethod lookupSingleTarget(
       AppView<?> appView, DexType invocationContext);
 
-  public abstract Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext);
+  // TODO(b/140204899): Refactor lookup methods to be defined in a single place.
+  public Collection<DexEncodedMethod> lookupTargets(
+      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
+    // Leverage exact receiver type if available.
+    DexEncodedMethod singleTarget = lookupSingleTarget(appView, invocationContext);
+    if (singleTarget != null) {
+      return ImmutableList.of(singleTarget);
+    }
+    if (!isInvokeMethodWithDynamicDispatch() || !appView.appInfo().hasLiveness()) {
+      return null;
+    }
+    Collection<DexEncodedMethod> targets;
+    if (isInvokeVirtual()) {
+      targets =
+          appView
+              .appInfo()
+              .resolveMethodOnClass(method.holder, method)
+              .lookupVirtualTargets(appView.appInfo());
+    } else {
+      assert isInvokeInterface();
+      targets =
+          appView
+              .appInfo()
+              .resolveMethodOnInterface(method.holder, method)
+              .lookupInterfaceTargets(appView.appInfo());
+    }
+    if (targets == null) {
+      return null;
+    }
+    DexType staticReceiverType = getInvokedMethod().holder;
+    DexType refinedReceiverType =
+        TypeAnalysis.getRefinedReceiverType(
+            appView.withLiveness(), this.asInvokeMethodWithReceiver());
+    // TODO(b/140204899): Instead of reprocessing here, pass refined receiver to lookup.
+    // Leverage refined receiver type if available.
+    if (refinedReceiverType != staticReceiverType) {
+      Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+      for (DexEncodedMethod target : targets) {
+        if (appView.isSubtype(target.method.holder, refinedReceiverType).isPossiblyTrue()) {
+          result.add(target);
+        }
+      }
+      return result;
+    }
+    return targets;
+  }
 
   public abstract InlineAction computeInlining(
       DexEncodedMethod singleTarget,
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 acd864e..0c4671e 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
@@ -7,7 +7,6 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeStaticRange;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -25,8 +24,6 @@
 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 java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -127,13 +124,6 @@
   }
 
   @Override
-  public Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
-    DexEncodedMethod target = appView.appInfo().lookupStaticTarget(getInvokedMethod());
-    return target == null ? Collections.emptyList() : Collections.singletonList(target);
-  }
-
-  @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeStatic(getInvokedMethod(), invocationContext);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index e866bf4..588a0f1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeSuperRange;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -18,8 +17,6 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 
 public class InvokeSuper extends InvokeMethodWithReceiver {
@@ -108,14 +105,6 @@
   }
 
   @Override
-  public Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
-    DexEncodedMethod target =
-        appView.appInfo().lookupSuperTarget(getInvokedMethod(), invocationContext);
-    return target == null ? Collections.emptyList() : Collections.singletonList(target);
-  }
-
-  @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeSuper(getInvokedMethod(), invocationContext);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 9ecc99d..da5a5e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -7,7 +7,6 @@
 
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.InvokeVirtualRange;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -21,11 +20,8 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 public class InvokeVirtual extends InvokeMethodWithReceiver {
 
@@ -107,34 +103,6 @@
   }
 
   @Override
-  public Collection<DexEncodedMethod> lookupTargets(
-      AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
-    // Leverage exact receiver type if available.
-    DexEncodedMethod singleTarget = lookupSingleTarget(appView, invocationContext);
-    if (singleTarget != null) {
-      return Collections.singletonList(singleTarget);
-    }
-    DexMethod method = getInvokedMethod();
-    Collection<DexEncodedMethod> targets =
-        appView
-            .appInfo()
-            .resolveMethodOnClass(method.holder, method)
-            .lookupVirtualTargets(appView.appInfo());
-    if (targets == null) {
-      return targets;
-    }
-    DexType staticReceiverType = getInvokedMethod().holder;
-    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView.withLiveness(), this);
-    // Leverage refined receiver type if available.
-    if (refinedReceiverType != staticReceiverType) {
-      return targets.stream()
-          .filter(m -> appView.isSubtype(m.method.holder, refinedReceiverType).isPossiblyTrue())
-          .collect(Collectors.toSet());
-    }
-    return targets;
-  }
-
-  @Override
   public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeVirtual(getInvokedMethod(), invocationContext);
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 7e15e72..d068677 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
@@ -706,6 +706,7 @@
     if (!options.isGeneratingClassFiles()) {
       printPhase("Class staticizer post processing");
       staticizeClasses(feedback, executorService);
+      feedback.updateVisibleOptimizationInfo();
     }
 
     // Build a new application with jumbo string info.
@@ -735,6 +736,9 @@
       builder.addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass(), true);
     }
 
+    // Update optimization info for all synthesized methods at once.
+    feedback.updateVisibleOptimizationInfo();
+
     if (outliner != null) {
       printPhase("Outlining");
       timing.begin("IR conversion phase 3");
@@ -755,6 +759,7 @@
               printMethod(code, "IR after outlining (SSA)", null);
               finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance());
             });
+        feedback.updateVisibleOptimizationInfo();
         assert outliner.checkAllOutlineSitesFoundAgain();
         builder.addSynthesizedClass(outlineClass, true);
         clearDexMethodCompilationState(outlineClass);
@@ -789,6 +794,8 @@
       }
     }
 
+    assert feedback.noUpdatesLeft();
+
     // Check if what we've added to the application builder as synthesized classes are same as
     // what we've added and used through AppInfo.
     assert appView
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 2649e62..0251620 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -290,21 +290,26 @@
       return false;
     }
 
-    if (!inliner.mainDexClasses.isEmpty()) {
-      // Don't inline code with references beyond root main dex classes into a root main dex class.
-      // If we do this it can increase the size of the main dex dependent classes.
-      if (inliner.mainDexClasses.getRoots().contains(method.method.holder)
-          && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
-              appView.appInfo(), singleTarget, inliner.mainDexClasses.getRoots())) {
-        whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
-        return false;
-      }
-      // Allow inlining into the classes in the main dex dependent set without restrictions.
+    // Don't inline code with references beyond root main dex classes into a root main dex class.
+    // If we do this it can increase the size of the main dex dependent classes.
+    if (reason != Reason.FORCE
+        && inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget)) {
+      whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
+      return false;
     }
-
+    assert reason != Reason.FORCE
+        || !inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget);
     return true;
   }
 
+  private boolean inlineeRefersToClassesNotInMainDex(DexType holder, DexEncodedMethod target) {
+    if (inliner.mainDexClasses.isEmpty() || !inliner.mainDexClasses.getRoots().contains(holder)) {
+      return false;
+    }
+    return MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
+        appView.appInfo(), target, inliner.mainDexClasses.getRoots());
+  }
+
   private boolean satisfiesRequirementsForSimpleInlining(
       InvokeMethod invoke, DexEncodedMethod target) {
     // If we are looking for a simple method, only inline if actually simple.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index d6f6855..cf4f72b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -102,11 +102,28 @@
   private void extractSwitchMap(DexEncodedField encodedField, IRCode initializer) {
     DexField field = encodedField.field;
     Int2ReferenceMap<DexField> switchMap = new Int2ReferenceArrayMap<>();
-    InstructionIterator it = initializer.instructionIterator();
-    Instruction insn;
-    Predicate<Instruction> predicate = i -> i.isStaticGet() && i.asStaticGet().getField() == field;
-    while ((insn = it.nextUntil(predicate)) != null) {
-      for (Instruction use : insn.outValue().uniqueUsers()) {
+
+    // Find each array-put instruction that updates an entry of the array that is stored in
+    // `encodedField`.
+    //
+    // We do this by finding each instruction that reads the array, and iterating the users of the
+    // out-value:
+    //
+    //   int[] x = static-get <encodedField>
+    //   x[index] = value
+    //
+    // A new array is assigned into `encodedField` in the beginning of `initializer`. For
+    // completeness, we also need to visit the users of the array being stored:
+    //
+    //   int[] x = new int[size];
+    //   static-put <encodedField>, x
+    //   x[index] = value
+    Predicate<Instruction> predicate =
+        i -> (i.isStaticGet() || i.isStaticPut()) && i.asFieldInstruction().getField() == field;
+    for (Instruction instruction : initializer.instructions(predicate)) {
+      Value valueOfInterest =
+          instruction.isStaticGet() ? instruction.outValue() : instruction.asStaticPut().value();
+      for (Instruction use : valueOfInterest.uniqueUsers()) {
         if (use.isArrayPut()) {
           Instruction index = use.asArrayPut().value().definition;
           if (index == null || !index.isConstNumber()) {
@@ -135,7 +152,7 @@
           if (switchMap.put(integerIndex, enumField) != null) {
             return;
           }
-        } else {
+        } else if (use != instruction) {
           return;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index a994f5d..2a80ce4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.BitSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -82,6 +83,16 @@
     processed.clear();
   }
 
+  public boolean noUpdatesLeft() {
+    assert fieldOptimizationInfos.isEmpty()
+        : StringUtils.join(fieldOptimizationInfos.keySet(), ", ");
+    assert methodOptimizationInfos.isEmpty()
+        : StringUtils.join(methodOptimizationInfos.keySet(), ", ");
+    assert processed.isEmpty()
+        : StringUtils.join(processed.keySet(), ", ");
+    return true;
+  }
+
   // FIELD OPTIMIZATION INFO:
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 45f5bed..829c227 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -153,6 +153,8 @@
   public final Set<DexType> neverClassInline;
   /** All types that *must* never be merged due to a configuration directive (testing only). */
   public final Set<DexType> neverMerge;
+  /** Set of const-class references. */
+  public final Set<DexType> constClassReferences;
   /**
    * All methods and fields whose value *must* never be propagated due to a configuration directive.
    * (testing only).
@@ -223,7 +225,8 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
-      Set<DexType> instantiatedLambdas) {
+      Set<DexType> instantiatedLambdas,
+      Set<DexType> constClassReferences) {
     super(application);
     this.liveTypes = liveTypes;
     this.instantiatedAnnotationTypes = instantiatedAnnotationTypes;
@@ -264,6 +267,7 @@
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
     this.instantiatedLambdas = instantiatedLambdas;
+    this.constClassReferences = constClassReferences;
   }
 
   public AppInfoWithLiveness(
@@ -304,7 +308,8 @@
       Set<DexType> prunedTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Map<DexType, Map<DexField, EnumValueInfo>> enumValueInfoMaps,
-      Set<DexType> instantiatedLambdas) {
+      Set<DexType> instantiatedLambdas,
+      Set<DexType> constClassReferences) {
     super(appInfoWithSubtyping);
     this.liveTypes = liveTypes;
     this.instantiatedAnnotationTypes = instantiatedAnnotationTypes;
@@ -345,6 +350,7 @@
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
     this.instantiatedLambdas = instantiatedLambdas;
+    this.constClassReferences = constClassReferences;
   }
 
   private AppInfoWithLiveness(AppInfoWithLiveness previous) {
@@ -398,7 +404,8 @@
             : CollectionUtils.mergeSets(previous.prunedTypes, removedClasses),
         previous.switchMaps,
         previous.enumValueInfoMaps,
-        previous.instantiatedLambdas);
+        previous.instantiatedLambdas,
+        previous.constClassReferences);
     copyMetadataFromPrevious(previous);
     assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
   }
@@ -485,6 +492,7 @@
             .collect(Collectors.toList()));
     this.switchMaps = rewriteReferenceKeys(previous.switchMaps, lense::lookupField);
     this.enumValueInfoMaps = rewriteReferenceKeys(previous.enumValueInfoMaps, lense::lookupType);
+    this.constClassReferences = previous.constClassReferences;
   }
 
   public AppInfoWithLiveness(
@@ -531,6 +539,7 @@
     this.prunedTypes = previous.prunedTypes;
     this.switchMaps = switchMaps;
     this.enumValueInfoMaps = enumValueInfoMaps;
+    this.constClassReferences = previous.constClassReferences;
     previous.markObsolete();
   }
 
@@ -547,6 +556,9 @@
     if (liveTypes.contains(type)) {
       return true;
     }
+    if (prunedTypes.contains(type)) {
+      return false;
+    }
     DexClass clazz = definitionFor(type);
     return clazz == null || !clazz.isProgramClass();
   }
@@ -589,6 +601,16 @@
     return interfaces;
   }
 
+  /**
+   * Const-classes is a conservative set of types that may be lock-candidates and cannot be merged.
+   * When using synchronized blocks, we cannot ensure that const-class locks will not flow in. This
+   * can potentially cause incorrect behavior when merging classes. A conservative choice is to not
+   * merge any const-class classes. More info at b/142438687.
+   */
+  public boolean isLockCandidate(DexType type) {
+    return constClassReferences.contains(type);
+  }
+
   public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
     assert checkIfObsolete();
     if (noLongerWrittenFields.isEmpty()) {
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 246dfa1..2306b28 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -273,6 +273,12 @@
   private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
 
   /**
+   * A set of seen const-class references that both serve as an initial lock-candidate set and will
+   * prevent statically merging the classes referenced.
+   */
+  private final Set<DexType> constClassReferences = Sets.newIdentityHashSet();
+
+  /**
    * A map from classes to annotations that need to be processed should the classes ever become
    * live.
    */
@@ -899,6 +905,15 @@
 
     @Override
     public boolean registerConstClass(DexType type) {
+      // 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);
+        }
+      }
       return registerConstClassOrCheckCast(type);
     }
 
@@ -2241,7 +2256,8 @@
             Collections.emptyMap(),
             Collections.emptyMap(),
             SetUtils.mapIdentityHashSet(
-                instantiatedInterfaceTypes.getItems(), DexProgramClass::getType));
+                instantiatedInterfaceTypes.getItems(), DexProgramClass::getType),
+            constClassReferences);
     appInfo.markObsolete();
     return appInfoWithLiveness;
   }
@@ -2644,11 +2660,12 @@
       if (encodedMethod == null) {
         return;
       }
+      KeepReason reason = KeepReason.reflectiveUseIn(method);
       if (encodedMethod.accessFlags.isStatic() || encodedMethod.accessFlags.isConstructor()) {
-        markDirectStaticOrConstructorMethodAsLive(
-            clazz, encodedMethod, KeepReason.reflectiveUseIn(method));
+        markMethodAsTargeted(clazz, encodedMethod, reason);
+        markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
       } else {
-        markVirtualMethodAsLive(clazz, encodedMethod, KeepReason.reflectiveUseIn(method));
+        markVirtualMethodAsLive(clazz, encodedMethod, reason);
       }
     }
   }
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 9326e31..bae5b40 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -342,6 +342,11 @@
         // We are not allowed to merge synchronized classes with synchronized methods.
         return false;
       }
+      if (appView.appInfo().constClassReferences.contains(clazz.type)) {
+        // Since the type is const-class referenced (and the static merger does not create a lense
+        // to map the merged type) the class will likely remain and there is no gain from merging.
+        return false;
+      }
       // Check if current candidate is a better choice depending on visibility. For package private
       // or protected, the key is parameterized by the package name already, so we just have to
       // check accessibility-flags. For global this is no-op.
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 4dc15ab..85da345 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -49,6 +49,7 @@
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
@@ -130,6 +131,7 @@
     ALWAYS_INLINE,
     CONFLICT,
     ILLEGAL_ACCESS,
+    MAIN_DEX_ROOT_OUTSIDE_REFERENCE,
     MERGE_ACROSS_NESTS,
     NATIVE_METHOD,
     NO_SIDE_EFFECTS,
@@ -137,8 +139,8 @@
     RESOLUTION_FOR_FIELDS_MAY_CHANGE,
     RESOLUTION_FOR_METHODS_MAY_CHANGE,
     SERVICE_LOADER,
+    SOURCE_AND_TARGET_LOCK_CANDIDATES,
     STATIC_INITIALIZERS,
-    STATIC_SYNCHRONIZED_METHODS,
     UNHANDLED_INVOKE_DIRECT,
     UNHANDLED_INVOKE_SUPER,
     UNSAFE_INLINING,
@@ -163,6 +165,9 @@
         case ILLEGAL_ACCESS:
           message = "it could lead to illegal accesses";
           break;
+        case MAIN_DEX_ROOT_OUTSIDE_REFERENCE:
+          message = "contains a constructor with a reference outside the main dex classes";
+          break;
         case MERGE_ACROSS_NESTS:
           message = "cannot merge across nests, or from nest to non-nest";
           break;
@@ -184,6 +189,9 @@
         case SERVICE_LOADER:
           message = "it is used by a service loader";
           break;
+        case SOURCE_AND_TARGET_LOCK_CANDIDATES:
+          message = "source and target are both lock-candidates";
+          break;
         case STATIC_INITIALIZERS:
           message = "merging of static initializers are not supported";
           break;
@@ -199,9 +207,6 @@
         case UNSUPPORTED_ATTRIBUTES:
           message = "since inner-class attributes are not supported";
           break;
-        case STATIC_SYNCHRONIZED_METHODS:
-          message = "since it has static synchronized methods which can lead to unwanted behavior";
-          break;
         default:
           assert false;
       }
@@ -378,6 +383,11 @@
         || appInfo.neverMerge.contains(clazz.type)) {
       return false;
     }
+
+    assert Streams.stream(Iterables.concat(clazz.fields(), clazz.methods()))
+        .map(KeyedDexItem::getKey)
+        .noneMatch(appInfo::isPinned);
+
     if (appView.options().featureSplitConfiguration != null &&
         appView.options().featureSplitConfiguration.isInFeature(clazz)) {
       // TODO(b/141452765): Allow class merging between classes in features.
@@ -410,21 +420,18 @@
       //     * Have access to the no-arg constructor of its first non-serializable superclass
       return false;
     }
-    for (DexEncodedField field : clazz.fields()) {
-      if (appInfo.isPinned(field.field)) {
-        return false;
-      }
-    }
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (appInfo.isPinned(method.method)) {
-        return false;
-      }
-      if (method.isInstanceInitializer() && disallowInlining(method, singleSubtype)) {
-        // Cannot guarantee that markForceInline() will work.
-        if (Log.ENABLED) {
-          AbortReason.UNSAFE_INLINING.printLogMessageForClass(clazz);
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      // We rename constructors to private methods and mark them to be forced-inlined, so we have to
+      // check if we can force-inline all constructors.
+      if (method.isInstanceInitializer()) {
+        AbortReason reason = disallowInlining(method, singleSubtype);
+        if (reason != null) {
+          // Cannot guarantee that markForceInline() will work.
+          if (Log.ENABLED) {
+            reason.printLogMessageForClass(clazz);
+          }
+          return false;
         }
-        return false;
       }
     }
     if (clazz.getEnclosingMethod() != null || !clazz.getInnerClasses().isEmpty()) {
@@ -473,6 +480,17 @@
       }
       return false;
     }
+    boolean sourceCanBeSynchronizedOn =
+        appView.appInfo().isLockCandidate(clazz.type) || clazz.hasStaticSynchronizedMethods();
+    boolean targetCanBeSynchronizedOn =
+        appView.appInfo().isLockCandidate(targetClass.type)
+            || targetClass.hasStaticSynchronizedMethods();
+    if (sourceCanBeSynchronizedOn && targetCanBeSynchronizedOn) {
+      if (Log.ENABLED) {
+        AbortReason.SOURCE_AND_TARGET_LOCK_CANDIDATES.printLogMessageForClass(clazz);
+      }
+      return false;
+    }
     if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
       // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
@@ -810,14 +828,6 @@
       assert isStillMergeCandidate(clazz);
     }
 
-    // Check for static synchronized methods on source
-    if (clazz.hasStaticSynchronizedMethods()) {
-      if (Log.ENABLED) {
-        AbortReason.STATIC_SYNCHRONIZED_METHODS.printLogMessageForClass(clazz);
-      }
-      return;
-    }
-
     // Guard against the case where we have two methods that may get the same signature
     // if we replace types. This is rare, so we approximate and err on the safe side here.
     if (new CollisionDetector(clazz.type, targetClass.type).mayCollide()) {
@@ -1665,7 +1675,7 @@
     }
   }
 
-  private boolean disallowInlining(DexEncodedMethod method, DexType invocationContext) {
+  private AbortReason disallowInlining(DexEncodedMethod method, DexType invocationContext) {
     if (appView.options().enableInlining) {
       if (method.getCode().isCfCode()) {
         CfCode code = method.getCode().asCfCode();
@@ -1675,11 +1685,21 @@
                 appView,
                 new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
                 invocationContext);
-        return constraint == ConstraintWithTarget.NEVER;
+        if (constraint == ConstraintWithTarget.NEVER) {
+          return AbortReason.UNSAFE_INLINING;
+        }
+        // Constructors can have references beyond the root main dex classes. This can increase the
+        // size of the main dex dependent classes and we should bail out.
+        if (mainDexClasses.getRoots().contains(invocationContext)
+            && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
+                appView.appInfo(), method, mainDexClasses.getRoots())) {
+          return AbortReason.MAIN_DEX_ROOT_OUTSIDE_REFERENCE;
+        }
+        return null;
       }
       // For non-jar/cf code we currently cannot guarantee that markForceInline() will succeed.
     }
-    return true;
+    return AbortReason.UNSAFE_INLINING;
   }
 
   private class SingleTypeMapperGraphLense extends GraphLense {
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java
index 3285930..1c4f406 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerInitTest.java
@@ -5,9 +5,7 @@
 package com.android.tools.r8.classmerging;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
@@ -43,7 +41,6 @@
   @Test
   public void testMergingClassWithConstructorNotInMainDex()
       throws IOException, CompilationFailedException, ExecutionException {
-    // TODO(b/142909854): Fix test expectation when bug has been resolved.
     testForR8(parameters.getBackend())
         .addInnerClasses(VerticalClassMergerInitTest.class)
         .addKeepMainRule(Main.class)
@@ -58,11 +55,11 @@
         .compile()
         .inspect(
             inspector -> {
-              assertThat(inspector.clazz(Base.class), not(isPresent()));
+              assertThat(inspector.clazz(Base.class), isPresent());
               assertThat(inspector.clazz(Child.class), isPresent());
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString("initialized"));
+        .assertSuccessWithOutputLines("Base.init()", "Outside.init()", "Child.init()");
   }
 
   public static class Base {
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java
index 9b3c384..2828ec4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockTest.java
@@ -6,10 +6,8 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.StringContains.containsString;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -51,18 +49,13 @@
   @Test
   public void testNoMergingOfClassUsedInMonitor()
       throws IOException, CompilationFailedException, ExecutionException {
-    // TODO(b/142438687): Fix expectation when fixed.
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(VerticalClassMergerSynchronizedBlockTest.class)
-            .addKeepMainRule(Main.class)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .run(parameters.getRuntime(), Main.class)
-            .assertFailureWithErrorThatMatches(containsString("DEADLOCKED!"));
-    if (result.getExitCode() == 0) {
-      result.inspect(inspector -> assertThat(inspector.clazz(LockOne.class), isPresent()));
-    }
+    testForR8(parameters.getBackend())
+        .addInnerClasses(VerticalClassMergerSynchronizedBlockTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput("Hello World!")
+        .inspect(inspector -> assertThat(inspector.clazz(LockOne.class), isPresent()));
   }
 
   abstract static class LockOne {}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java
new file mode 100644
index 0000000..b6039db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerSynchronizedBlockWithArraysTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.io.IOException;
+import java.lang.Thread.State;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergerSynchronizedBlockWithArraysTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // The 4.0.4 runtime will flakily mark threads as blocking and report DEADLOCKED.
+    return getTestParameters()
+        .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public VerticalClassMergerSynchronizedBlockWithArraysTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testOnRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+        .addInnerClasses(VerticalClassMergerSynchronizedBlockWithArraysTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput("Hello World!");
+  }
+
+  @Test
+  public void testNoMergingOfClassUsedInMonitor()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(VerticalClassMergerSynchronizedBlockWithArraysTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput("Hello World!")
+        .inspect(inspector -> assertThat(inspector.clazz(LockOne.class), isPresent()));
+  }
+
+  abstract static class LockOne {}
+
+  static class LockTwo extends LockOne {}
+
+  static class LockThree {}
+
+  public static class Main {
+
+    private static volatile boolean inLockThreeCritical = false;
+    private static volatile boolean inLockTwoCritical = false;
+    private static volatile boolean arnoldWillNotBeBack = false;
+
+    private static volatile Thread t1 = new Thread(Main::lockThreeThenOne);
+    private static volatile Thread t2 = new Thread(Main::lockTwoThenThree);
+    private static volatile Thread t3 = new Thread(Main::arnold);
+
+    static void synchronizedAccessThroughLocks(String arg) {
+      System.out.print(arg);
+    }
+
+    public static void main(String[] args) {
+      t1.start();
+      t2.start();
+      // This thread is started to ensure termination in case we are rewriting incorrectly.
+      t3.start();
+
+      while (!arnoldWillNotBeBack) {}
+    }
+
+    static void lockThreeThenOne() {
+      synchronized (LockThree[].class) {
+        inLockThreeCritical = true;
+        while (!inLockTwoCritical) {}
+        synchronized (LockOne[].class) {
+          synchronizedAccessThroughLocks("Hello ");
+        }
+      }
+    }
+
+    static void lockTwoThenThree() {
+      synchronized (LockTwo[].class) {
+        inLockTwoCritical = true;
+        while (!inLockThreeCritical) {}
+        synchronized (LockThree[].class) {
+          synchronizedAccessThroughLocks("World!");
+        }
+      }
+    }
+
+    static void arnold() {
+      while (t1.getState() != State.TERMINATED || t2.getState() != State.TERMINATED) {
+        if (t1.getState() == State.BLOCKED && t2.getState() == State.BLOCKED) {
+          System.err.println("DEADLOCKED!");
+          System.exit(1);
+          break;
+        }
+      }
+      arnoldWillNotBeBack = true;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index f0c6a78..99d27fa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -65,7 +65,7 @@
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     // TODO(b/112831361): support for class staticizer in CF backend.
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   public ClassStaticizerTest(TestParameters parameters) {
@@ -95,7 +95,7 @@
             .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
             .addOptionsModification(this::configure)
             .allowAccessModification()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), main)
             .assertSuccessWithOutput(javaOutput);
 
@@ -109,7 +109,9 @@
             "STATIC: String trivial.TrivialTestClass.next()"),
         references(clazz, "testSimple", "void"));
 
-    assertTrue(instanceMethods(inspector.clazz(Simple.class)).isEmpty());
+    ClassSubject simple = inspector.clazz(Simple.class);
+    assertTrue(instanceMethods(simple).isEmpty());
+    assertThat(simple.clinit(), not(isPresent()));
 
     assertEquals(
         Lists.newArrayList(
@@ -119,7 +121,9 @@
             "STATIC: String trivial.TrivialTestClass.next()"),
         references(clazz, "testSimpleWithPhi", "void", "int"));
 
-    assertTrue(instanceMethods(inspector.clazz(SimpleWithPhi.class)).isEmpty());
+    ClassSubject simpleWithPhi = inspector.clazz(SimpleWithPhi.class);
+    assertTrue(instanceMethods(simpleWithPhi).isEmpty());
+    assertThat(simpleWithPhi.clinit(), not(isPresent()));
 
     assertEquals(
         Lists.newArrayList(
@@ -128,7 +132,9 @@
             "STATIC: String trivial.TrivialTestClass.next()"),
         references(clazz, "testSimpleWithParams", "void"));
 
-    assertTrue(instanceMethods(inspector.clazz(SimpleWithParams.class)).isEmpty());
+    ClassSubject simpleWithParams = inspector.clazz(SimpleWithParams.class);
+    assertTrue(instanceMethods(simpleWithParams).isEmpty());
+    assertThat(simpleWithParams.clinit(), not(isPresent()));
 
     assertEquals(
         Lists.newArrayList(
@@ -139,7 +145,10 @@
             "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE"),
         references(clazz, "testSimpleWithSideEffects", "void"));
 
-    assertTrue(instanceMethods(inspector.clazz(SimpleWithSideEffects.class)).isEmpty());
+    ClassSubject simpleWithSideEffects = inspector.clazz(SimpleWithSideEffects.class);
+    assertTrue(instanceMethods(simpleWithSideEffects).isEmpty());
+    // As its name implies, its clinit has side effects.
+    assertThat(simpleWithSideEffects.clinit(), isPresent());
 
     // TODO(b/111832046): add support for singleton instance getters.
     assertEquals(
@@ -151,7 +160,9 @@
             "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE"),
         references(clazz, "testSimpleWithGetter", "void"));
 
-    assertFalse(instanceMethods(inspector.clazz(SimpleWithGetter.class)).isEmpty());
+    ClassSubject simpleWithGetter = inspector.clazz(SimpleWithGetter.class);
+    assertFalse(instanceMethods(simpleWithGetter).isEmpty());
+    assertThat(simpleWithGetter.clinit(), isPresent());
   }
 
   @Test
@@ -172,7 +183,7 @@
             .allowAccessModification()
             .noMinification()
             .addOptionsModification(this::configure)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), main);
 
     CodeInspector inspector = result.inspector();
@@ -182,7 +193,7 @@
         Lists.newArrayList(),
         references(clazz, "testOk_fieldOnly", "void"));
 
-    assertFalse(inspector.clazz(CandidateOkFieldOnly.class).isPresent());
+    assertThat(inspector.clazz(CandidateOkFieldOnly.class), not(isPresent()));
   }
 
   @Test
@@ -210,7 +221,7 @@
             .allowAccessModification()
             .noMinification()
             .addOptionsModification(this::configure)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), main)
             .assertSuccessWithOutput(javaOutput);
 
@@ -226,7 +237,7 @@
             "STATIC: void movetohost.HostOk.blah(String)"),
         references(clazz, "testOk", "void"));
 
-    assertFalse(inspector.clazz(CandidateOk.class).isPresent());
+    assertThat(inspector.clazz(CandidateOk.class), not(isPresent()));
 
     assertEquals(
         Lists.newArrayList(
@@ -237,7 +248,7 @@
             "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
         references(clazz, "testOkSideEffects", "void"));
 
-    assertFalse(inspector.clazz(CandidateOkSideEffects.class).isPresent());
+    assertThat(inspector.clazz(CandidateOkSideEffects.class), not(isPresent()));
 
     assertEquals(
         Lists.newArrayList(
@@ -249,7 +260,7 @@
             "VIRTUAL: String movetohost.HostConflictMethod.bar(String)"),
         references(clazz, "testConflictMethod", "void"));
 
-    assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+    assertThat(inspector.clazz(CandidateConflictMethod.class), isPresent());
 
     assertEquals(
         Lists.newArrayList(
@@ -260,12 +271,12 @@
             "String movetohost.CandidateConflictField.field"),
         references(clazz, "testConflictField", "void"));
 
-    assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+    assertThat(inspector.clazz(CandidateConflictMethod.class), isPresent());
   }
 
   private List<String> instanceMethods(ClassSubject clazz) {
     assertNotNull(clazz);
-    assertTrue(clazz.isPresent());
+    assertThat(clazz, isPresent());
     return Streams.stream(clazz.getDexClass().methods())
         .filter(method -> !method.isStatic())
         .map(method -> method.method.toSourceString())
@@ -276,7 +287,7 @@
   private List<String> references(
       ClassSubject clazz, String methodName, String retValue, String... params) {
     assertNotNull(clazz);
-    assertTrue(clazz.isPresent());
+    assertThat(clazz, isPresent());
 
     MethodSignature signature = new MethodSignature(methodName, retValue, params);
     DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
@@ -335,7 +346,7 @@
             .allowAccessModification()
             .noMinification()
             .addOptionsModification(this::configure)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), main)
             .assertSuccessWithOutput(javaOutput);
 
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 59aae43..b9ee37a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -32,7 +32,10 @@
     final String mainClassName = ex1.getClassName();
     final String extraRules =
         keepMainMethod(mainClassName) + neverInlineMethod(mainClassName, testMethodSignature);
-    runTest(FOLDER, mainClassName, extraRules,
+    runTest(
+        FOLDER,
+        mainClassName,
+        extraRules,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject clazz = checkClassIsKept(codeInspector, ex1.getClassName());
@@ -40,8 +43,7 @@
           MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
           long ifzCount =
               testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
-          long paramNullCheckCount =
-              countCall(testMethod, "ArrayIteratorKt", "checkParameterIsNotNull");
+          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);
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
new file mode 100644
index 0000000..0cf779d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ProgramAnnotationRemovalTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ProgramAnnotationRemovalTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(ProgramAnnotationRemovalTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepAttributes("RuntimeVisibleAnnotations")
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .run(parameters.getRuntime(), TestClass.class);
+
+    CodeInspector inspector = result.inspector();
+
+    ClassSubject liveAnnotationClassSubject = inspector.clazz(LiveProgramAnnotation.class);
+    assertThat(liveAnnotationClassSubject, isPresent());
+
+    ClassSubject deadAnnotationClassSubject = inspector.clazz(DeadProgramAnnotation.class);
+    assertThat(deadAnnotationClassSubject, not(isPresent()));
+
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject methodWithLiveProgramAnnotationSubject =
+        testClassSubject.uniqueMethodWithName("methodWithLiveProgramAnnotation");
+    assertThat(methodWithLiveProgramAnnotationSubject, isPresent());
+    assertEquals(1, methodWithLiveProgramAnnotationSubject.getMethod().annotations.size());
+
+    MethodSubject methodWithDeadProgramAnnotationSubject =
+        testClassSubject.uniqueMethodWithName("methodWithDeadProgramAnnotation");
+    assertThat(methodWithDeadProgramAnnotationSubject, isPresent());
+    assertEquals(0, methodWithDeadProgramAnnotationSubject.getMethod().annotations.size());
+
+    result.assertSuccessWithOutputLines("@" + liveAnnotationClassSubject.getFinalName() + "()");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) throws Exception {
+      methodWithLiveProgramAnnotation();
+      methodWithDeadProgramAnnotation();
+
+      // Do something with LiveProgramAnnotation to ensure it becomes live.
+      if (System.currentTimeMillis() <= 0) {
+        System.out.println(LiveProgramAnnotation.class);
+      }
+    }
+
+    @NeverInline
+    @LiveProgramAnnotation
+    static void methodWithLiveProgramAnnotation() throws Exception {
+      Method method = TestClass.class.getDeclaredMethod("methodWithLiveProgramAnnotation");
+      for (Annotation annotation : method.getDeclaredAnnotations()) {
+        System.out.println(annotation);
+      }
+    }
+
+    @NeverInline
+    @DeadProgramAnnotation
+    static void methodWithDeadProgramAnnotation() throws Exception {
+      Method method = TestClass.class.getDeclaredMethod("methodWithDeadProgramAnnotation");
+      for (Annotation annotation : method.getDeclaredAnnotations()) {
+        System.out.println(annotation);
+      }
+    }
+  }
+
+  @Target(ElementType.METHOD)
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface LiveProgramAnnotation {}
+
+  @Target(ElementType.METHOD)
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface DeadProgramAnnotation {}
+}
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 426fef4..d93e3c0 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -148,6 +148,21 @@
                     metavar='BENCHMARKNAME',
                     help='Print the sizes of individual dex segments as ' +
                         '\'<BENCHMARKNAME>-<segment>(CodeSize): <bytes>\'')
+  result.add_option('--track-time-in-memory',
+                    help='Plot the times taken from memory starting point to '
+                         'end-point with defined memory increment',
+                    default=False,
+                    action='store_true')
+  result.add_option('--track-time-in-memory-max',
+                    help='Setting the maximum memory baseline to run in',
+                    type='int')
+  result.add_option('--track-time-in-memory-min',
+                    help='Setting the minimum memory baseline to run in',
+                    type='int')
+  result.add_option('--track-time-in-memory-increment',
+                    help='Setting the increment',
+                    type='int',
+                    default=32)
 
   return result.parse_args(argv)
 
@@ -287,6 +302,37 @@
         value = utils.cat_file_on_cloud_storage(gs_location, ignore_errors=True)
         print('%s\n' % value)
 
+def track_time_in_memory(options, args):
+  # Args will be destroyed
+  assert len(args) == 0
+  if not options.track_time_in_memory_min:
+    raise Exception(
+        'You have to specify --track_time_in_memory_min when running with '
+        '--track-time-in-memory')
+  if not options.track_time_in_memory_max:
+    raise Exception(
+        'You have to specify --track_time_in_memory_max when running with '
+        '--track-time-in-memory')
+  if not options.track_time_in_memory_increment:
+    raise Exception(
+        'You have to specify --track_time_in_memory_increment when running '
+        'with --track-time-in-memory')
+  current = options.track_time_in_memory_min
+  print('Memory (KB)\tTime (ms)')
+  with utils.TempDir() as temp:
+    stdout = os.path.join(temp, 'stdout')
+    stdout_fd = open(stdout, 'w')
+    while current <= options.track_time_in_memory_max:
+      extra_args = ['-Xmx%sM' % current]
+      t0 = time.time()
+      exit_code = run_with_options(options, [], extra_args, stdout_fd, quiet=True)
+      t1 = time.time()
+      total = (1000.0 * (t1 - t0)) if exit_code == 0 else -1
+      print('%s\t%s' % (current, total))
+      current += options.track_time_in_memory_increment
+
+  return 0
+
 def main(argv):
   (options, args) = ParseOptions(argv)
   if options.expect_oom and not options.max_memory:
@@ -295,10 +341,15 @@
   if options.expect_oom and options.timeout:
     raise Exception(
         'You should not use --timeout when also specifying --expect-oom')
+  if options.find_min_xmx and options.track_time_in_memory:
+    raise Exception(
+        'You cannot both find the min xmx and track time at the same time')
   if options.run_all:
     return run_all(options, args)
   if options.find_min_xmx:
     return find_min_xmx(options, args)
+  if options.track_time_in_memory:
+    return track_time_in_memory(options, args)
   exit_code = run_with_options(options, args)
   if options.expect_oom:
     exit_code = 0 if exit_code == OOM_EXIT_CODE else 1
@@ -335,7 +386,7 @@
     return 'deploy' if options.compiler == 'r8' else 'proguarded'
   return options.type
 
-def run_with_options(options, args, extra_args=None):
+def run_with_options(options, args, extra_args=None, stdout=None, quiet=False):
   if extra_args is None:
     extra_args = []
   app_provided_pg_conf = False;
@@ -476,14 +527,18 @@
             profile=options.profile,
             track_memory_file=options.track_memory_to_file,
             extra_args=extra_args,
+            stdout=stdout,
             stderr=stderr,
-            timeout=options.timeout)
+            timeout=options.timeout,
+            quiet=quiet)
       if exit_code != 0:
         with open(stderr_path) as stderr:
           stderr_text = stderr.read()
-          print(stderr_text)
+          if not quiet:
+            print(stderr_text)
           if 'java.lang.OutOfMemoryError' in stderr_text:
-            print('Failure was OOM')
+            if not quiet:
+              print('Failure was OOM')
             return OOM_EXIT_CODE
           return exit_code
 
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 10da97d..2784ea5 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -11,7 +11,7 @@
 
 def run(tool, args, build=None, debug=True,
         profile=False, track_memory_file=None, extra_args=None,
-        stderr=None, stdout=None, return_stdout=False, timeout=0):
+        stderr=None, stdout=None, return_stdout=False, timeout=0, quiet=False):
   if build is None:
     build, args = extract_build_from_args(args)
   if build:
@@ -36,7 +36,7 @@
   if lib:
     cmd.extend(["--lib", lib])
   cmd.extend(args)
-  utils.PrintCmd(cmd)
+  utils.PrintCmd(cmd, quiet=quiet)
   if timeout > 0:
     kill = lambda process: process.kill()
     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)