Merge commit 'bb3db97402a1650c05d9c50e813c89972adfa6ad' into 1.7.11-dev
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index f34e2bc..200240c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -464,9 +464,6 @@
       Position callerPosition,
       Origin origin) {
     checkIfObsolete();
-    if (accessFlags.isSynchronized()) {
-      throw new Unreachable("Invalid attempt to build synchronized method for inlining");
-    }
     return code.buildInliningIR(
         context, this, appView, valueNumberGenerator, callerPosition, origin);
   }
@@ -1011,7 +1008,10 @@
     checkIfObsolete();
     assert !accessFlags.isStatic();
     Builder builder =
-        builder(this).promoteToStatic().unsetOptimizationInfo().withoutThisParameter();
+        builder(this)
+            .promoteToStatic()
+            .withoutThisParameter()
+            .adjustOptimizationInfoAfterRemovingThisParameter();
     DexEncodedMethod method = builder.build();
     method.copyMetadata(this);
     setObsolete();
@@ -1274,11 +1274,6 @@
       return this;
     }
 
-    public Builder unsetOptimizationInfo() {
-      optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
-      return this;
-    }
-
     public Builder withoutThisParameter() {
       assert code != null;
       if (code.isDexCode()) {
@@ -1289,6 +1284,14 @@
       return this;
     }
 
+    public Builder adjustOptimizationInfoAfterRemovingThisParameter() {
+      if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+        optimizationInfo.asUpdatableMethodOptimizationInfo()
+            .adjustOptimizationInfoAfterRemovingThisParameter();
+      }
+      return this;
+    }
+
     public void setCode(Code code) {
       this.code = code;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 0ca8fac..e540f39 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -145,7 +145,8 @@
         boolean encodedValueStorageIsLive;
         if (enqueuer.isFieldLive(encodedValueStorage)) {
           if (enqueuer.isFieldRead(encodedValueStorage)
-              || enqueuer.isFieldWrittenOutsideDefaultConstructor(encodedValueStorage)) {
+              || enqueuer.isFieldWrittenOutsideDefaultConstructor(encodedValueStorage)
+              || reachesMapOrRequiredField(protoFieldInfo)) {
             // Mark that the field is both read and written by reflection such that we do not
             // (i) optimize field reads into loading the default value of the field or (ii) remove
             // field writes to proto fields that could be read using reflection by the proto
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 fa9384a..78018f9 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
@@ -214,6 +214,13 @@
     return normals.build();
   }
 
+  public int numberOfNormalSuccessors() {
+    if (hasCatchHandlers()) {
+      return successors.size() - catchHandlers.getUniqueTargets().size();
+    }
+    return successors.size();
+  }
+
   public boolean hasUniquePredecessor() {
     return predecessors.size() == 1;
   }
@@ -893,11 +900,40 @@
     catchHandlers = new CatchHandlers<>(guards, successorIndexes);
   }
 
-  public void addCatchHandler(BasicBlock rethrowBlock, DexType guard) {
-    assert !hasCatchHandlers();
-    getMutableSuccessors().add(0, rethrowBlock);
-    rethrowBlock.getMutablePredecessors().add(this);
-    catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
+  public void appendCatchHandler(BasicBlock target, DexType guard) {
+    if (!canThrow()) {
+      // Nothing to catch.
+      return;
+    }
+    if (hasCatchHandlers()) {
+      if (catchHandlers.getGuards().contains(guard)) {
+        // Subsumed by an existing catch handler.
+        return;
+      }
+      int targetIndex = successors.indexOf(target);
+      if (targetIndex < 0) {
+        List<BasicBlock> successors = getMutableSuccessors();
+        int numberOfSuccessors = successors.size();
+        int numberOfNormalSuccessors = numberOfNormalSuccessors();
+        if (numberOfNormalSuccessors > 0) {
+          // Increase the size of the successor list by 1, and increase the index of each normal
+          // successor by 1.
+          targetIndex = numberOfSuccessors - numberOfNormalSuccessors;
+          successors.add(targetIndex, target);
+        } else {
+          // If there are no normal successors we can simply add the new catch handler.
+          targetIndex = successors.size();
+          successors.add(target);
+        }
+        target.getMutablePredecessors().add(this);
+      }
+      catchHandlers = catchHandlers.appendGuard(guard, targetIndex);
+    } else {
+      assert instructions.stream().filter(Instruction::instructionTypeCanThrow).count() == 1;
+      getMutableSuccessors().add(0, target);
+      target.getMutablePredecessors().add(this);
+      catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
+    }
   }
 
   /**
@@ -1795,8 +1831,7 @@
         continue;
       }
       BasicBlock catchSuccessor = fromBlock.successors.get(prevCatchTarget);
-      // We assume that all the catch handlers targets has only one
-      // predecessor and, thus, no phis.
+      // We assume that all the catch handlers targets has only one predecessor and, thus, no phis.
       assert catchSuccessor.getPredecessors().size() == 1;
       assert catchSuccessor.getPhis().isEmpty();
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 237c0cc..95ee7e0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -76,6 +76,13 @@
         && getGuards().get(getGuards().size() - 1) == factory.throwableType;
   }
 
+  public CatchHandlers<T> appendGuard(DexType guard, T target) {
+    assert !guards.contains(guard);
+    List<DexType> newGuards = ImmutableList.<DexType>builder().addAll(guards).add(guard).build();
+    List<T> newTargets = ImmutableList.<T>builder().addAll(targets).add(target).build();
+    return new CatchHandlers<>(newGuards, newTargets);
+  }
+
   public CatchHandlers<T> removeGuard(DexType guardToBeRemoved) {
     List<DexType> newGuards = new ArrayList<>();
     List<T> newTargets = new ArrayList<>();
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 fee2c94..ce02bf6 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
@@ -13,12 +13,15 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -133,11 +136,6 @@
     return metadata;
   }
 
-  public void mergeMetadataFromInlinee(IRCode inlinee) {
-    assert !inlinee.metadata.mayHaveMonitorInstruction();
-    this.metadata.merge(inlinee.metadata);
-  }
-
   public BasicBlock entryBlock() {
     return blocks.getFirst();
   }
@@ -286,6 +284,53 @@
     return false;
   }
 
+  /**
+   * Prepares every block for getting catch handlers. This involves splitting blocks until each
+   * block has only one throwing instruction, and all instructions after the throwing instruction
+   * are allowed to follow a throwing instruction.
+   *
+   * <p>It is also a requirement that the entry block does not have any catch handlers, thus the
+   * entry block is split to ensure that it contains no throwing instructions.
+   *
+   * <p>This method also inserts a split block between each block with more than two predecessors
+   * and the predecessors that have a throwing instruction. This is necessary because adding catch
+   * handlers to a predecessor would otherwise lead to critical edges.
+   */
+  public void prepareBlocksForCatchHandlers() {
+    BasicBlock entryBlock = entryBlock();
+    ListIterator<BasicBlock> blockIterator = listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      InstructionListIterator instructionIterator = block.listIterator(this);
+      boolean hasSeenThrowingInstruction = false;
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        boolean instructionTypeCanThrow = instruction.instructionTypeCanThrow();
+        if ((hasSeenThrowingInstruction && !instruction.isAllowedAfterThrowingInstruction())
+            || (instructionTypeCanThrow && block == entryBlock)) {
+          instructionIterator.previous();
+          instructionIterator.split(this, blockIterator);
+          blockIterator.previous();
+          break;
+        }
+        if (instructionTypeCanThrow) {
+          hasSeenThrowingInstruction = true;
+        }
+      }
+      if (hasSeenThrowingInstruction) {
+        List<BasicBlock> successors = block.getSuccessors();
+        if (successors.size() == 1 && ListUtils.first(successors).getPredecessors().size() > 1) {
+          BasicBlock splitBlock = block.createSplitBlock(getHighestBlockNumber() + 1, true);
+          Goto newGoto = new Goto(block);
+          newGoto.setPosition(Position.none());
+          splitBlock.listIterator(this).add(newGoto);
+          blockIterator.add(splitBlock);
+        }
+      }
+    }
+    assert blocks.stream().allMatch(block -> block.numberOfThrowingInstructions() <= 1);
+  }
+
   public void splitCriticalEdges() {
     List<BasicBlock> newBlocks = new ArrayList<>();
     int nextBlockNumber = getHighestBlockNumber() + 1;
@@ -871,6 +916,10 @@
     return this::instructionIterator;
   }
 
+  public <T extends Instruction> Iterable<T> instructions(Predicate<Instruction> predicate) {
+    return () -> IteratorUtils.filter(instructionIterator(), predicate);
+  }
+
   public InstructionIterator instructionIterator() {
     return new IRCodeInstructionIterator(this);
   }
@@ -959,6 +1008,10 @@
     return createValue(typeLattice, null);
   }
 
+  public Phi createPhi(BasicBlock block, TypeLatticeElement type) {
+    return new Phi(valueNumberGenerator.next(), block, type, null, RegisterReadType.NORMAL);
+  }
+
   public ConstNumber createIntConstant(int value) {
     Value out = createValue(TypeLatticeElement.INT);
     return new ConstNumber(out, value);
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 31a1562..5bddf13 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
@@ -880,6 +880,10 @@
     return false;
   }
 
+  public boolean isMonitorEnter() {
+    return false;
+  }
+
   public Monitor asMonitor() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index a0eda99..78b9b64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -99,6 +99,11 @@
   }
 
   @Override
+  public boolean isMonitorEnter() {
+    return isEnter();
+  }
+
+  @Override
   public Monitor asMonitor() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 07c99e0..33b06c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -239,6 +239,10 @@
     return definition;
   }
 
+  public boolean hasAliasedValue() {
+    return getAliasedValue() != this;
+  }
+
   /**
    * If this value is defined by an instruction that defines an alias of another value, such as the
    * {@link Assume} instruction, then the incoming value to the {@link Assume} instruction is
@@ -989,11 +993,6 @@
         || typeLattice.nullability().isDefinitelyNotNull();
   }
 
-  public boolean canBeNull() {
-    assert typeLattice.isReference();
-    return typeLattice.isNullable();
-  }
-
   public void markAsArgument() {
     assert !isArgument;
     assert !isThis;
@@ -1004,6 +1003,20 @@
     return isArgument;
   }
 
+  public int computeArgumentPosition(IRCode code) {
+    assert isArgument;
+    int position = 0;
+    InstructionIterator instructionIterator = code.entryBlock().iterator();
+    while (instructionIterator.hasNext()) {
+      Instruction instruction = instructionIterator.next();
+      assert instruction.isArgument();
+      if (instruction.outValue() == this) {
+        return position;
+      }
+      position++;
+    }
+    throw new Unreachable();
+  }
 
   public boolean knownToBeBoolean() {
     return knownToBeBoolean(null);
@@ -1199,10 +1212,20 @@
       lattice = aliasedValue.definition.asAssumeDynamicType().getAssumption().getType();
 
       // For precision, verify that the dynamic type is at least as precise as the static type.
-      assert lattice.lessThanOrEqualUpToNullability(getTypeLattice(), appView);
+      assert lattice.lessThanOrEqualUpToNullability(typeLattice, appView)
+          : typeLattice + " < " + lattice;
     } else {
       // Otherwise, simply use the static type.
-      lattice = getTypeLattice();
+      lattice = typeLattice;
+    }
+
+    // Account for nullability, which could be flown from non-null assumption in between dynamic
+    // type assumption or simply from array/object creation.
+    if (typeLattice.isDefinitelyNotNull() && lattice.isNullable()) {
+      // Having non-null assumption means it is a reference type.
+      assert lattice.isReference();
+      // Then, we can return the non-null variant of dynamic type if both assumptions are aliased.
+      return lattice.asReferenceTypeLatticeElement().asMeetWithNotNull();
     }
     return lattice;
   }
@@ -1226,7 +1249,11 @@
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
     if (aliasedValue != null) {
-      return aliasedValue.definition.asAssumeDynamicType().getAssumption().getLowerBoundType();
+      ClassTypeLatticeElement lattice =
+          aliasedValue.definition.asAssumeDynamicType().getAssumption().getLowerBoundType();
+      return lattice != null && typeLattice.isDefinitelyNotNull() && lattice.isNullable()
+          ? lattice.asMeetWithNotNull().asClassTypeLatticeElement()
+          : lattice;
     }
     return null;
   }
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 57425ab..dd3416f 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
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
-import static com.android.tools.r8.ir.optimize.CodeRewriter.checksNullBeforeSideEffect;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -26,17 +25,10 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
-import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.fieldaccess.FieldBitAccessAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.FieldValueAnalysis;
-import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
-import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
 import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
@@ -74,6 +66,7 @@
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -107,7 +100,6 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -171,6 +163,8 @@
 
   final DeadCodeRemover deadCodeRemover;
 
+  final MethodOptimizationInfoCollector methodOptimizationInfoCollector;
+
   private final OptimizationFeedbackDelayed delayedOptimizationFeedback =
       new OptimizationFeedbackDelayed();
   private final OptimizationFeedback simpleOptimizationFeedback =
@@ -211,6 +205,7 @@
             .map(prefix -> "L" + DescriptorUtils.getPackageBinaryNameFromJavaType(prefix))
             .map(options.itemFactory::createString)
             .collect(Collectors.toList());
+    this.methodOptimizationInfoCollector = new MethodOptimizationInfoCollector(appView);
     if (options.isDesugaredLibraryCompilation()) {
       // Specific L8 Settings.
       // BackportedMethodRewriter is needed for retarget core library members and backports.
@@ -1403,7 +1398,7 @@
         appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
       }
       // Computation of non-null parameters on normal exits rely on the existence of non-null IRs.
-      nonNullTracker.computeNonNullParamOnNormalExits(feedback, code);
+      methodOptimizationInfoCollector.computeNonNullParamOnNormalExits(feedback, code);
     }
     if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) {
       codeRewriter.removeAssumeInstructions(code);
@@ -1426,21 +1421,25 @@
       // Compute optimization info summary for the current method unless it is pinned
       // (in that case we should not be making any assumptions about the behavior of the method).
       if (!appView.appInfo().withLiveness().isPinned(method.method)) {
-        codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
-        codeRewriter.identifyParameterUsages(method, code, feedback);
-        codeRewriter.identifyReturnsArgument(method, code, feedback);
-        codeRewriter.identifyTrivialInitializer(method, code, feedback);
+        methodOptimizationInfoCollector.identifyClassInlinerEligibility(method, code, feedback);
+        methodOptimizationInfoCollector.identifyParameterUsages(method, code, feedback);
+        methodOptimizationInfoCollector.identifyReturnsArgument(method, code, feedback);
+        methodOptimizationInfoCollector.identifyTrivialInitializer(method, code, feedback);
 
         if (options.enableInlining && inliner != null) {
-          codeRewriter.identifyInvokeSemanticsForInlining(method, code, appView, feedback);
+          methodOptimizationInfoCollector
+              .identifyInvokeSemanticsForInlining(method, code, appView, feedback);
         }
 
-        computeDynamicReturnType(feedback, method, code);
+        methodOptimizationInfoCollector
+            .computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
         FieldValueAnalysis.run(appView, code, feedback, method);
-        computeInitializedClassesOnNormalExit(feedback, method, code);
-        computeMayHaveSideEffects(feedback, method, code);
-        computeReturnValueOnlyDependsOnArguments(feedback, method, code);
-        computeNonNullParamOrThrow(feedback, method, code);
+        methodOptimizationInfoCollector
+            .computeInitializedClassesOnNormalExit(feedback, method, code);
+        methodOptimizationInfoCollector.computeMayHaveSideEffects(feedback, method, code);
+        methodOptimizationInfoCollector
+            .computeReturnValueOnlyDependsOnArguments(feedback, method, code);
+        methodOptimizationInfoCollector.computeNonNullParamOrThrow(feedback, method, code);
       }
     }
 
@@ -1471,164 +1470,6 @@
     finalizeIR(method, code, feedback);
   }
 
-  // Track usage of parameters and compute their nullability and possibility of NPE.
-  private void computeNonNullParamOrThrow(
-      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
-      return;
-    }
-
-    List<Value> arguments = code.collectArguments();
-    BitSet paramsCheckedForNull = new BitSet();
-    for (int index = 0; index < arguments.size(); index++) {
-      Value argument = arguments.get(index);
-      // This handles cases where the parameter is checked via Kotlin Intrinsics:
-      //
-      //   kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
-      //
-      // or its inlined version:
-      //
-      //   if (param != null) return;
-      //   invoke-static throwParameterIsNullException(msg)
-      //
-      // or some other variants, e.g., throw null or NPE after the direct null check.
-      if (argument.isUsed() && checksNullBeforeSideEffect(code, argument, appView)) {
-        paramsCheckedForNull.set(index);
-      }
-    }
-    if (paramsCheckedForNull.length() > 0) {
-      feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
-    }
-  }
-
-  private void computeDynamicReturnType(
-      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    if (dynamicTypeOptimization != null) {
-      DexType staticReturnTypeRaw = method.method.proto.returnType;
-      if (!staticReturnTypeRaw.isReferenceType()) {
-        return;
-      }
-
-      TypeLatticeElement dynamicReturnType =
-          dynamicTypeOptimization.computeDynamicReturnType(method, code);
-      if (dynamicReturnType != null) {
-        TypeLatticeElement staticReturnType =
-            TypeLatticeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
-
-        // If the dynamic return type is not more precise than the static return type there is no
-        // need to record it.
-        if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) {
-          feedback.methodReturnsObjectOfType(method, appView, dynamicReturnType);
-        }
-      }
-
-      ClassTypeLatticeElement exactReturnType =
-          dynamicTypeOptimization.computeDynamicLowerBoundType(method, code);
-      if (exactReturnType != null) {
-        feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType);
-      }
-    }
-  }
-
-  private void computeInitializedClassesOnNormalExit(
-      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    if (options.enableInitializedClassesAnalysis && appView.appInfo().hasLiveness()) {
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      Set<DexType> initializedClasses =
-          InitializedClassesOnNormalExitAnalysis.computeInitializedClassesOnNormalExit(
-              appViewWithLiveness, code);
-      if (initializedClasses != null && !initializedClasses.isEmpty()) {
-        feedback.methodInitializesClassesOnNormalExit(method, initializedClasses);
-      }
-    }
-  }
-
-  private void computeMayHaveSideEffects(
-      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    // If the method is native, we don't know what could happen.
-    assert !method.accessFlags.isNative();
-
-    if (!options.enableSideEffectAnalysis) {
-      return;
-    }
-
-    if (appView.appInfo().withLiveness().mayHaveSideEffects.containsKey(method.method)) {
-      return;
-    }
-
-    DexType context = method.method.holder;
-
-    if (method.isClassInitializer()) {
-      // For class initializers, we also wish to compute if the class initializer has observable
-      // side effects.
-      ClassInitializerSideEffect classInitializerSideEffect =
-          ClassInitializerSideEffectAnalysis.classInitializerCanBePostponed(appView, code);
-      if (classInitializerSideEffect.isNone()) {
-        feedback.methodMayNotHaveSideEffects(method);
-        feedback.classInitializerMayBePostponed(method);
-      } else if (classInitializerSideEffect.canBePostponed()) {
-        feedback.classInitializerMayBePostponed(method);
-      }
-      return;
-    }
-
-    boolean mayHaveSideEffects;
-    if (method.accessFlags.isSynchronized()) {
-      // If the method is synchronized then it acquires a lock.
-      mayHaveSideEffects = true;
-    } else if (method.isInstanceInitializer() && hasNonTrivialFinalizeMethod(context)) {
-      // If a class T overrides java.lang.Object.finalize(), then treat the constructor as having
-      // side effects. This ensures that we won't remove instructions on the form `new-instance
-      // {v0}, T`.
-      mayHaveSideEffects = true;
-    } else {
-      // Otherwise, check if there is an instruction that has side effects.
-      mayHaveSideEffects =
-          Streams.stream(code.instructions())
-              .anyMatch(instruction -> instruction.instructionMayHaveSideEffects(appView, context));
-    }
-
-    if (!mayHaveSideEffects) {
-      feedback.methodMayNotHaveSideEffects(method);
-    }
-  }
-
-  private void computeReturnValueOnlyDependsOnArguments(
-      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    if (!options.enableDeterminismAnalysis) {
-      return;
-    }
-    boolean returnValueOnlyDependsOnArguments =
-        DeterminismAnalysis.returnValueOnlyDependsOnArguments(appView.withLiveness(), code);
-    if (returnValueOnlyDependsOnArguments) {
-      feedback.methodReturnValueOnlyDependsOnArguments(method);
-    }
-  }
-
-  // Returns true if `method` is an initializer and the enclosing class overrides the method
-  // `void java.lang.Object.finalize()`.
-  private boolean hasNonTrivialFinalizeMethod(DexType type) {
-    DexClass clazz = appView.definitionFor(type);
-    if (clazz != null) {
-      if (clazz.isProgramClass() && !clazz.isInterface()) {
-        ResolutionResult resolutionResult =
-            appView
-                .appInfo()
-                .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMethods.finalize);
-        for (DexEncodedMethod target : resolutionResult.asListOfTargets()) {
-          if (target.method != appView.dexItemFactory().objectMethods.finalize) {
-            return true;
-          }
-        }
-        return false;
-      } else {
-        // Conservatively report that the library class could implement finalize().
-        return true;
-      }
-    }
-    return false;
-  }
-
   private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 708cabb8..d60ac1f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -172,6 +172,16 @@
   private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
     DexMethod methodToInstall =
         methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+    if (dexClass.isInterface()
+        && originalMethod.isDefaultMethod()
+        && !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
+      // Interface method desugaring has been performed before and all the call-backs will be
+      // generated in all implementors of the interface. R8 cannot introduce new
+      // default methods at this point, but R8 does not need to do anything (the interface
+      // already implements the vivified version through inheritance, and all implementors
+      // support the call-back correctly).
+      return;
+    }
     CfCode cfCode =
         new APIConverterWrapperCfCodeProvider(
             appView, originalMethod.method, null, this, dexClass.isInterface())
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 7c1590c..932c817 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
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query.DIRECTLY;
 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.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
@@ -15,12 +14,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 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.DexEncodedMethod.ClassInlinerEligibility;
-import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
-import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.ThrowableMethods;
@@ -28,7 +22,6 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -54,7 +47,6 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.InstanceOf;
-import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -74,7 +66,6 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Switch;
@@ -85,17 +76,12 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
-import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness.EnumValueInfo;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.AssertionProcessing;
 import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LongInterval;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.base.Supplier;
@@ -140,8 +126,6 @@
 import java.util.Map;
 import java.util.PriorityQueue;
 import java.util.Set;
-import java.util.function.BiFunction;
-import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class CodeRewriter {
@@ -441,7 +425,7 @@
           BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition(), guard, appView);
       code.blocks.add(rethrowBlock);
       // Add catch handler to the block containing the last recursive call.
-      newBlock.addCatchHandler(rethrowBlock, guard);
+      newBlock.appendCatchHandler(rethrowBlock, guard);
     }
   }
 
@@ -1155,614 +1139,6 @@
     assert code.isConsistentGraph();
   }
 
-  public void identifyReturnsArgument(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    List<BasicBlock> normalExits = code.computeNormalExitBlocks();
-    if (normalExits.isEmpty()) {
-      feedback.methodNeverReturnsNormally(method);
-      return;
-    }
-    Return firstExit = normalExits.get(0).exit().asReturn();
-    if (firstExit.isReturnVoid()) {
-      return;
-    }
-    Value returnValue = firstExit.returnValue();
-    boolean isNeverNull = returnValue.getTypeLattice().isReference() && returnValue.isNeverNull();
-    for (int i = 1; i < normalExits.size(); i++) {
-      Return exit = normalExits.get(i).exit().asReturn();
-      Value value = exit.returnValue();
-      if (value != returnValue) {
-        returnValue = null;
-      }
-      isNeverNull &= value.getTypeLattice().isReference() && value.isNeverNull();
-    }
-    if (returnValue != null) {
-      if (returnValue.isArgument()) {
-        // Find the argument number.
-        int index = code.collectArguments().indexOf(returnValue);
-        assert index != -1;
-        feedback.methodReturnsArgument(method, index);
-      }
-      if (returnValue.isConstant()) {
-        if (returnValue.definition.isConstNumber()) {
-          long value = returnValue.definition.asConstNumber().getRawValue();
-          feedback.methodReturnsConstantNumber(method, value);
-        } else if (returnValue.definition.isConstString()) {
-          ConstString constStringInstruction = returnValue.definition.asConstString();
-          if (!constStringInstruction.instructionInstanceCanThrow()) {
-            feedback.methodReturnsConstantString(method, constStringInstruction.getValue());
-          }
-        }
-      }
-    }
-    if (isNeverNull) {
-      feedback.methodNeverReturnsNull(method);
-    }
-  }
-
-  public void identifyInvokeSemanticsForInlining(
-      DexEncodedMethod method, IRCode code, AppView<?> appView, OptimizationFeedback feedback) {
-    if (method.isStatic()) {
-      // Identifies if the method preserves class initialization after inlining.
-      feedback.markTriggerClassInitBeforeAnySideEffect(
-          method, triggersClassInitializationBeforeSideEffect(method.method.holder, code, appView));
-    } else {
-      // Identifies if the method preserves null check of the receiver after inlining.
-      final Value receiver = code.getThis();
-      feedback.markCheckNullReceiverBeforeAnySideEffect(
-          method, receiver.isUsed() && checksNullBeforeSideEffect(code, receiver, appView));
-    }
-  }
-
-  public void identifyClassInlinerEligibility(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    // Method eligibility is calculated in similar way for regular method
-    // and for the constructor. To be eligible method should only be using its
-    // receiver in the following ways:
-    //
-    //  (1) as a receiver of reads/writes of instance fields of the holder class
-    //  (2) as a return value
-    //  (3) as a receiver of a call to the superclass initializer. Note that we don't
-    //      check what is passed to superclass initializer as arguments, only check
-    //      that it is not the instance being initialized.
-    //
-    boolean instanceInitializer = method.isInstanceInitializer();
-    if (method.accessFlags.isNative()
-        || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)
-        || method.accessFlags.isSynchronized()) {
-      return;
-    }
-
-    feedback.setClassInlinerEligibility(method, null);  // To allow returns below.
-
-    Value receiver = code.getThis();
-    if (receiver.numberOfPhiUsers() > 0) {
-      return;
-    }
-
-    DexClass clazz = appView.definitionFor(method.method.holder);
-    if (clazz == null) {
-      return;
-    }
-
-    boolean receiverUsedAsReturnValue = false;
-    boolean seenSuperInitCall = false;
-    for (Instruction insn : receiver.uniqueUsers()) {
-      if (insn.isReturn()) {
-        receiverUsedAsReturnValue = true;
-        continue;
-      }
-
-      if (insn.isInstanceGet() || insn.isInstancePut()) {
-        if (insn.isInstancePut()) {
-          InstancePut instancePutInstruction = insn.asInstancePut();
-          // Only allow field writes to the receiver.
-          if (instancePutInstruction.object() != receiver) {
-            return;
-          }
-          // Do not allow the receiver to escape via a field write.
-          if (instancePutInstruction.value() == receiver) {
-            return;
-          }
-        }
-        DexField field = insn.asFieldInstruction().getField();
-        if (field.holder == clazz.type && clazz.lookupInstanceField(field) != null) {
-          // Require only accessing instance fields of the *current* class.
-          continue;
-        }
-        return;
-      }
-
-      // If this is an instance initializer allow one call to superclass instance initializer.
-      if (insn.isInvokeDirect()) {
-        InvokeDirect invokedDirect = insn.asInvokeDirect();
-        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
-        if (dexItemFactory.isConstructor(invokedMethod) &&
-            invokedMethod.holder == clazz.superType &&
-            invokedDirect.inValues().lastIndexOf(receiver) == 0 &&
-            !seenSuperInitCall &&
-            instanceInitializer) {
-          seenSuperInitCall = true;
-          continue;
-        }
-        // We don't support other direct calls yet.
-        return;
-      }
-
-      // Other receiver usages make the method not eligible.
-      return;
-    }
-
-    if (instanceInitializer && !seenSuperInitCall) {
-      // Call to super constructor not found?
-      return;
-    }
-
-    feedback.setClassInlinerEligibility(
-        method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
-  }
-
-  public void identifyTrivialInitializer(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    if (!method.isInstanceInitializer() && !method.isClassInitializer()) {
-      return;
-    }
-
-    if (method.accessFlags.isNative()) {
-      return;
-    }
-
-    DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
-    if (clazz == null) {
-      return;
-    }
-
-    feedback.setTrivialInitializer(
-        method,
-        method.isInstanceInitializer()
-            ? computeInstanceInitializerInfo(code, clazz, appView::definitionFor)
-            : computeClassInitializerInfo(code, clazz));
-  }
-
-  public void identifyParameterUsages(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
-    List<ParameterUsage> usages = new ArrayList<>();
-    List<Value> values = code.collectArguments();
-    for (int i = 0; i < values.size(); i++) {
-      Value value = values.get(i);
-      if (value.numberOfPhiUsers() > 0) {
-        continue;
-      }
-      ParameterUsage usage = collectParameterUsages(i, value);
-      if (usage != null) {
-        usages.add(usage);
-      }
-    }
-    feedback.setParameterUsages(method, usages.isEmpty() ? null : new ParameterUsagesInfo(usages));
-  }
-
-  private ParameterUsage collectParameterUsages(int i, Value value) {
-    ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
-    for (Instruction user : value.uniqueUsers()) {
-      if (!builder.note(user)) {
-        return null;
-      }
-    }
-    return builder.build();
-  }
-
-  // This method defines trivial instance initializer as follows:
-  //
-  // ** The initializer may call the initializer of the base class, which
-  //    itself must be trivial.
-  //
-  // ** java.lang.Object.<init>() is considered trivial.
-  //
-  // ** all arguments passed to a super-class initializer must be non-throwing
-  //    constants or arguments.
-  //
-  // ** Assigns arguments or non-throwing constants to fields of this class.
-  //
-  // (Note that this initializer does not have to have zero arguments.)
-  private TrivialInitializer computeInstanceInitializerInfo(
-      IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
-    Value receiver = code.getThis();
-
-    for (Instruction insn : code.instructions()) {
-      if (insn.isReturn()) {
-        continue;
-      }
-
-      if (insn.isArgument()) {
-        continue;
-      }
-
-      if (insn.isConstInstruction()) {
-        if (insn.instructionInstanceCanThrow()) {
-          return null;
-        } else {
-          continue;
-        }
-      }
-
-      if (insn.isInvokeDirect()) {
-        InvokeDirect invokedDirect = insn.asInvokeDirect();
-        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
-
-        if (invokedMethod.holder != clazz.superType) {
-          return null;
-        }
-
-        // java.lang.Object.<init>() is considered trivial.
-        if (invokedMethod == dexItemFactory.objectMethods.constructor) {
-          continue;
-        }
-
-        DexClass holder = typeToClass.apply(invokedMethod.holder);
-        if (holder == null) {
-          return null;
-        }
-
-        DexEncodedMethod callTarget = holder.lookupDirectMethod(invokedMethod);
-        if (callTarget == null ||
-            !callTarget.isInstanceInitializer() ||
-            callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null ||
-            invokedDirect.getReceiver() != receiver) {
-          return null;
-        }
-
-        for (Value value : invokedDirect.inValues()) {
-          if (value != receiver && !(value.isConstant() || value.isArgument())) {
-            return null;
-          }
-        }
-        continue;
-      }
-
-      if (insn.isInstancePut()) {
-        InstancePut instancePut = insn.asInstancePut();
-        DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
-        if (field == null ||
-            instancePut.object() != receiver ||
-            (instancePut.value() != receiver && !instancePut.value().isArgument())) {
-          return null;
-        }
-        continue;
-      }
-
-      if (insn.isGoto()) {
-        // Trivial goto to the next block.
-        if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
-          continue;
-        }
-        return null;
-      }
-
-      // Other instructions make the instance initializer not eligible.
-      return null;
-    }
-
-    return TrivialInstanceInitializer.INSTANCE;
-  }
-
-  // This method defines trivial class initializer as follows:
-  //
-  // ** The initializer may only instantiate an instance of the same class,
-  //    initialize it with a call to a trivial constructor *without* arguments,
-  //    and assign this instance to a static final field of the same class.
-  //
-  private synchronized TrivialInitializer computeClassInitializerInfo(IRCode code, DexClass clazz) {
-    Value createdSingletonInstance = null;
-    DexField singletonField = null;
-
-    for (Instruction insn : code.instructions()) {
-      if (insn.isConstNumber()) {
-        continue;
-      }
-
-      if (insn.isConstString()) {
-        if (insn.instructionInstanceCanThrow()) {
-          return null;
-        }
-        continue;
-      }
-
-      if (insn.isReturn()) {
-        continue;
-      }
-
-      if (insn.isNewInstance()) {
-        NewInstance newInstance = insn.asNewInstance();
-        if (createdSingletonInstance != null ||
-            newInstance.clazz != clazz.type ||
-            insn.outValue() == null) {
-          return null;
-        }
-        createdSingletonInstance = insn.outValue();
-        continue;
-      }
-
-      if (insn.isInvokeDirect()) {
-        InvokeDirect invokedDirect = insn.asInvokeDirect();
-        if (createdSingletonInstance == null ||
-            invokedDirect.getReceiver() != createdSingletonInstance) {
-          return null;
-        }
-
-        DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
-        if (callTarget == null ||
-            !callTarget.isInstanceInitializer() ||
-            !callTarget.method.proto.parameters.isEmpty() ||
-            callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null) {
-          return null;
-        }
-        continue;
-      }
-
-      if (insn.isStaticPut()) {
-        StaticPut staticPut = insn.asStaticPut();
-        if (singletonField != null
-            || createdSingletonInstance == null
-            || staticPut.value() != createdSingletonInstance) {
-          return null;
-        }
-
-        DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
-        if (field == null ||
-            !field.accessFlags.isStatic() ||
-            !field.accessFlags.isFinal()) {
-          return null;
-        }
-        singletonField = field.field;
-        continue;
-      }
-
-      // Other instructions make the class initializer not eligible.
-      return null;
-    }
-
-    return singletonField == null ? null : new TrivialClassInitializer(singletonField);
-  }
-
-  /**
-   * An enum used to classify instructions according to a particular effect that they produce.
-   *
-   * The "effect" of an instruction can be seen as a program state change (or semantic change) at
-   * runtime execution. For example, an instruction could cause the initialization of a class,
-   * change the value of a field, ... while other instructions do not.
-   *
-   * This classification also depends on the type of analysis that is using it. For instance, an
-   * analysis can look for instructions that cause class initialization while another look for
-   * instructions that check nullness of a particular object.
-   *
-   * On the other hand, some instructions may provide a non desired effect which is a signal for
-   * the analysis to stop.
-   */
-  private enum InstructionEffect {
-    DESIRED_EFFECT,
-    CONDITIONAL_EFFECT,
-    OTHER_EFFECT,
-    NO_EFFECT
-  }
-
-  /**
-   * Returns true if the given code unconditionally throws if value is null before any other side
-   * effect instruction.
-   *
-   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
-   */
-  public static boolean checksNullBeforeSideEffect(IRCode code, Value value, AppView<?> appView) {
-    return alwaysTriggerExpectedEffectBeforeAnythingElse(
-        code,
-        (instr, it) -> {
-          BasicBlock currentBlock = instr.getBlock();
-          // If the code explicitly checks the nullability of the value, we should visit the next
-          // block that corresponds to the null value where NPE semantic could be preserved.
-          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.
-              for (BasicBlock predecessor : currentBlock.getPredecessors()) {
-                if (isNullCheck(predecessor.exit(), value)) {
-                  return InstructionEffect.DESIRED_EFFECT;
-                }
-              }
-              // 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())) {
-            it.next(); // Skip call to NullPointerException.<init>.
-            return InstructionEffect.NO_EFFECT;
-          } else if (instr.throwsNpeIfValueIsNull(value, appView.dexItemFactory())) {
-            // In order to preserve NPE semantic, the exception must not be caught by any handler.
-            // Therefore, we must ignore this instruction if it is covered by a catch handler.
-            // Note: this is a conservative approach where we consider that any catch handler could
-            // catch the exception, even if it cannot catch a NullPointerException.
-            if (!currentBlock.hasCatchHandlers()) {
-              // We found a NPE check on the value.
-              return InstructionEffect.DESIRED_EFFECT;
-            }
-          } else if (instr.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
-            // If the current instruction is const-string, this could load the parameter name.
-            // Just make sure it is indeed not throwing.
-            if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
-              return InstructionEffect.NO_EFFECT;
-            }
-            // We found a side effect before a NPE check.
-            return InstructionEffect.OTHER_EFFECT;
-          }
-          return InstructionEffect.NO_EFFECT;
-        });
-  }
-
-  // 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) {
-    if (!instr.isInvokeStatic()) {
-      return false;
-    }
-    // We need to strip 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.graphLense().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;
-  }
-
-  private static boolean isNullCheck(Instruction instr, Value value) {
-    return instr.isIf() && instr.asIf().isZeroTest()
-        && instr.inValues().get(0).equals(value)
-        && (instr.asIf().getType() == Type.EQ || instr.asIf().getType() == Type.NE);
-  }
-
-  /**
-   * Returns true if the given instruction is {@code v <- new-instance NullPointerException}, and
-   * the next instruction is {@code invoke-direct v, NullPointerException.<init>()}.
-   */
-  private static boolean isInstantiationOfNullPointerException(
-      Instruction instruction, InstructionIterator it, DexItemFactory dexItemFactory) {
-    if (!instruction.isNewInstance()
-        || instruction.asNewInstance().clazz != dexItemFactory.npeType) {
-      return false;
-    }
-    Instruction next = it.peekNext();
-    if (next == null
-        || !next.isInvokeDirect()
-        || next.asInvokeDirect().getInvokedMethod() != dexItemFactory.npeMethods.init) {
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * Returns true if the given code unconditionally triggers class initialization before any other
-   * side effecting instruction.
-   *
-   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
-   */
-  private static boolean triggersClassInitializationBeforeSideEffect(
-      DexType clazz, IRCode code, AppView<?> appView) {
-    return alwaysTriggerExpectedEffectBeforeAnythingElse(
-        code,
-        (instruction, it) -> {
-          DexType context = code.method.method.holder;
-          if (instruction.definitelyTriggersClassInitialization(
-              clazz, context, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
-            // In order to preserve class initialization semantic, the exception must not be caught
-            // by any handler. Therefore, we must ignore this instruction if it is covered by a
-            // catch handler.
-            // Note: this is a conservative approach where we consider that any catch handler could
-            // catch the exception, even if it cannot catch an ExceptionInInitializerError.
-            if (!instruction.getBlock().hasCatchHandlers()) {
-              // We found an instruction that preserves initialization of the class.
-              return InstructionEffect.DESIRED_EFFECT;
-            }
-          } else if (instruction.instructionMayHaveSideEffects(appView, clazz)) {
-            // We found a side effect before class initialization.
-            return InstructionEffect.OTHER_EFFECT;
-          }
-          return InstructionEffect.NO_EFFECT;
-        });
-  }
-
-  /**
-   * Returns true if the given code unconditionally triggers an expected effect before anything
-   * else, false otherwise.
-   *
-   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
-   */
-  private static boolean alwaysTriggerExpectedEffectBeforeAnythingElse(
-      IRCode code, BiFunction<Instruction, InstructionIterator, InstructionEffect> function) {
-    final int color = code.reserveMarkingColor();
-    try {
-      ArrayDeque<BasicBlock> worklist = new ArrayDeque<>();
-      final BasicBlock entry = code.entryBlock();
-      worklist.add(entry);
-      entry.mark(color);
-
-      while (!worklist.isEmpty()) {
-        BasicBlock currentBlock = worklist.poll();
-        assert currentBlock.isMarked(color);
-
-        InstructionEffect result = InstructionEffect.NO_EFFECT;
-        InstructionIterator it = currentBlock.iterator();
-        while (result == InstructionEffect.NO_EFFECT && it.hasNext()) {
-          result = function.apply(it.next(), it);
-        }
-        if (result == InstructionEffect.OTHER_EFFECT) {
-          // We found an instruction that is causing an unexpected side effect.
-          return false;
-        } else if (result == InstructionEffect.DESIRED_EFFECT) {
-          // The current path is causing the expected effect. No need to go deeper in this path,
-          // go to the next block in the work list.
-          continue;
-        } else if (result == InstructionEffect.CONDITIONAL_EFFECT) {
-          assert !currentBlock.getNormalSuccessors().isEmpty();
-          Instruction lastInstruction = currentBlock.getInstructions().getLast();
-          assert lastInstruction.isIf();
-          // The current path is checking if the value of interest is null. Go deeper into the path
-          // that corresponds to the null value.
-          BasicBlock targetIfReceiverIsNull = lastInstruction.asIf().targetFromCondition(0);
-          if (!targetIfReceiverIsNull.isMarked(color)) {
-            worklist.add(targetIfReceiverIsNull);
-            targetIfReceiverIsNull.mark(color);
-          }
-        } else {
-          assert result == InstructionEffect.NO_EFFECT;
-          // The block did not cause any particular effect.
-          if (currentBlock.getNormalSuccessors().isEmpty()) {
-            // This is the end of the current non-exceptional path and we did not find any expected
-            // effect. It means there is at least one path where the expected effect does not
-            // happen.
-            Instruction lastInstruction = currentBlock.getInstructions().getLast();
-            assert lastInstruction.isReturn() || lastInstruction.isThrow();
-            return false;
-          } else {
-            // Look into successors
-            for (BasicBlock successor : currentBlock.getSuccessors()) {
-              if (!successor.isMarked(color)) {
-                worklist.add(successor);
-                successor.mark(color);
-              }
-            }
-          }
-        }
-      }
-
-      // If we reach this point, we checked that the expected effect happens in every possible path.
-      return true;
-    } finally {
-      code.returnMarkingColor(color);
-    }
-  }
-
   private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
     // TODO(sgjesse): Insert cast if required.
     TypeLatticeElement returnType =
@@ -2197,11 +1573,6 @@
     }
   }
 
-  private boolean canBeFolded(Instruction instruction) {
-    return (instruction.isBinop() && instruction.asBinop().canBeFolded()) ||
-        (instruction.isUnop() && instruction.asUnop().canBeFolded());
-  }
-
   // Split constants that flow into ranged invokes. This gives the register allocator more
   // freedom in assigning register to ranged invokes which can greatly reduce the number
   // of register needed (and thereby code size as well).
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 263da6c..e0d77fb 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
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
+import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
@@ -21,6 +24,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
@@ -264,12 +268,6 @@
       return false;
     }
 
-    // Don't inline if target is synchronized.
-    if (singleTarget.accessFlags.isSynchronized()) {
-      whyAreYouNotInliningReporter.reportSynchronizedMethod();
-      return false;
-    }
-
     if (reason == Reason.DUAL_CALLER) {
       if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
         // When we have a method with two call sites, we simply inline the method as we normally do
@@ -552,58 +550,129 @@
 
   @Override
   public boolean willExceedBudget(
+      IRCode code,
+      InvokeMethod invoke,
       InlineeWithReason inlinee,
       BasicBlock block,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     if (inlinee.reason.mustBeInlined()) {
       return false;
     }
+    return willExceedInstructionBudget(inlinee, whyAreYouNotInliningReporter)
+        || willExceedMonitorEnterValuesBudget(code, invoke, inlinee, whyAreYouNotInliningReporter)
+        || willExceedControlFlowResolutionBlocksBudget(
+            inlinee, block, whyAreYouNotInliningReporter);
+  }
 
-    if (block.hasCatchHandlers() && inlinee.reason != Reason.FORCE) {
-      // Inlining could lead to an explosion of move-exception and resolution moves. As an
-      // example, consider the following piece of code.
-      //   try {
-      //     ...
-      //     foo();
-      //     ...
-      //   } catch (A e) { ... }
-      //   } catch (B e) { ... }
-      //   } catch (C e) { ... }
-      //
-      // The generated code for the above example will have a move-exception instruction
-      // for each of the three catch handlers. Furthermore, the blocks with these move-
-      // exception instructions may require a number of resolution moves to setup the
-      // register state for the catch handlers. When inlining foo(), the generated code
-      // will have a move-exception instruction *for each of the instructions in foo()
-      // that can throw*, along with the necessary resolution moves for each exception-
-      // edge. We therefore abort inlining if the number of exception-edges explode.
-      int numberOfThrowingInstructionsInInlinee = 0;
-      for (BasicBlock inlineeBlock : inlinee.code.blocks) {
-        numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions();
-      }
-      // Estimate the number of "control flow resolution blocks", where we will insert a
-      // move-exception instruction (if needed), along with all the resolution moves that
-      // will be needed to setup the register state for the catch handler.
-      int estimatedNumberOfControlFlowResolutionBlocks =
-          numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
-      // Abort if inlining could lead to an explosion in the number of control flow
-      // resolution blocks that setup the register state before the actual catch handler.
-      int threshold = appView.options().inliningControlFlowResolutionBlocksThreshold;
-      if (estimatedNumberOfControlFlowResolutionBlocks >= threshold) {
-        whyAreYouNotInliningReporter
-            .reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
-                estimatedNumberOfControlFlowResolutionBlocks, threshold);
-        return true;
-      }
-    }
-
+  private boolean willExceedInstructionBudget(
+      InlineeWithReason inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     int numberOfInstructions = Inliner.numberOfInstructions(inlinee.code);
     if (instructionAllowance < Inliner.numberOfInstructions(inlinee.code)) {
       whyAreYouNotInliningReporter.reportWillExceedInstructionBudget(
           numberOfInstructions, instructionAllowance);
       return true;
     }
+    return false;
+  }
 
+  /**
+   * If inlining would lead to additional lock values in the caller, then check that the number of
+   * lock values after inlining would not exceed the threshold.
+   *
+   * <p>The motivation for limiting the number of locks in a given method is that the register
+   * allocator will attempt to pin a register for each lock value. Thus, if a method has many locks,
+   * many registers will be pinned, which will lead to high register pressure.
+   */
+  private boolean willExceedMonitorEnterValuesBudget(
+      IRCode code,
+      InvokeMethod invoke,
+      InlineeWithReason inlinee,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+    if (!code.metadata().mayHaveMonitorInstruction()) {
+      return false;
+    }
+
+    if (!inlinee.code.metadata().mayHaveMonitorInstruction()) {
+      return false;
+    }
+
+    Set<DexType> constantMonitorEnterValues = Sets.newIdentityHashSet();
+    Set<Value> nonConstantMonitorEnterValues = Sets.newIdentityHashSet();
+    collectAllMonitorEnterValues(code, constantMonitorEnterValues, nonConstantMonitorEnterValues);
+    if (constantMonitorEnterValues.isEmpty() && nonConstantMonitorEnterValues.isEmpty()) {
+      return false;
+    }
+
+    for (Monitor monitor : inlinee.code.<Monitor>instructions(Instruction::isMonitorEnter)) {
+      Value monitorEnterValue = monitor.object().getAliasedValue();
+      if (monitorEnterValue.isArgument()) {
+        monitorEnterValue =
+            invoke
+                .arguments()
+                .get(monitorEnterValue.computeArgumentPosition(inlinee.code))
+                .getAliasedValue();
+      }
+      addMonitorEnterValue(
+          monitorEnterValue, constantMonitorEnterValues, nonConstantMonitorEnterValues);
+    }
+
+    int numberOfMonitorEnterValuesAfterInlining =
+        constantMonitorEnterValues.size() + nonConstantMonitorEnterValues.size();
+    int threshold = appView.options().inliningMonitorEnterValuesAllowance;
+    if (numberOfMonitorEnterValuesAfterInlining > threshold) {
+      whyAreYouNotInliningReporter.reportWillExceedMonitorEnterValuesBudget(
+          numberOfMonitorEnterValuesAfterInlining, threshold);
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Inlining could lead to an explosion of move-exception and resolution moves. As an example,
+   * consider the following piece of code.
+   *
+   * <pre>
+   *   try {
+   *     ...
+   *     foo();
+   *     ...
+   *   } catch (A e) { ... }
+   *   } catch (B e) { ... }
+   *   } catch (C e) { ... }
+   * </pre>
+   *
+   * <p>The generated code for the above example will have a move-exception instruction for each of
+   * the three catch handlers. Furthermore, the blocks with these move-exception instructions may
+   * require a number of resolution moves to setup the register state for the catch handlers. When
+   * inlining foo(), the generated code will have a move-exception instruction *for each of the
+   * instructions in foo() that can throw*, along with the necessary resolution moves for each
+   * exception-edge. We therefore abort inlining if the number of exception-edges explode.
+   */
+  private boolean willExceedControlFlowResolutionBlocksBudget(
+      InlineeWithReason inlinee,
+      BasicBlock block,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+    if (!block.hasCatchHandlers()) {
+      return false;
+    }
+    int numberOfThrowingInstructionsInInlinee = 0;
+    for (BasicBlock inlineeBlock : inlinee.code.blocks) {
+      numberOfThrowingInstructionsInInlinee += inlineeBlock.numberOfThrowingInstructions();
+    }
+    // Estimate the number of "control flow resolution blocks", where we will insert a
+    // move-exception instruction (if needed), along with all the resolution moves that
+    // will be needed to setup the register state for the catch handler.
+    int estimatedNumberOfControlFlowResolutionBlocks =
+        numberOfThrowingInstructionsInInlinee * block.numberOfCatchHandlers();
+    // Abort if inlining could lead to an explosion in the number of control flow
+    // resolution blocks that setup the register state before the actual catch handler.
+    int threshold = appView.options().inliningControlFlowResolutionBlocksThreshold;
+    if (estimatedNumberOfControlFlowResolutionBlocks >= threshold) {
+      whyAreYouNotInliningReporter.reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
+          estimatedNumberOfControlFlowResolutionBlocks, threshold);
+      return true;
+    }
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index cbaa36f..fb1ef58 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -97,6 +97,8 @@
 
   @Override
   public boolean willExceedBudget(
+      IRCode code,
+      InvokeMethod invoke,
       InlineeWithReason inlinee,
       BasicBlock block,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 44c72c5..fef9538 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -3,20 +3,27 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.google.common.base.Predicates.not;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessFlags;
 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;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
@@ -24,6 +31,9 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.MoveException;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
@@ -41,6 +51,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -575,14 +586,26 @@
         AppView<? extends AppInfoWithSubtyping> appView,
         Position callerPosition,
         LensCodeRewriter lensCodeRewriter) {
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      InternalOptions options = appView.options();
       Origin origin = appView.appInfo().originFor(target.method.holder);
 
       // Build the IR for a yet not processed method, and perform minimal IR processing.
       IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
 
-      // Insert a null check if this is needed to preserve the implicit null check for the
-      // receiver.
-      if (shouldSynthesizeNullCheckForReceiver) {
+      // Insert a null check if this is needed to preserve the implicit null check for the receiver.
+      // This is only needed if we do not also insert a monitor-enter instruction, since that will
+      // throw a NPE if the receiver is null.
+      //
+      // Note: When generating DEX, we synthesize monitor-enter/exit instructions during IR
+      // building, and therefore, we do not need to do anything here. Upon writing, we will use the
+      // flag "declared synchronized" instead of "synchronized".
+      boolean shouldSynthesizeMonitorEnterExit =
+          target.accessFlags.isSynchronized() && options.isGeneratingClassFiles();
+      boolean isSynthesizingNullCheckForReceiverUsingMonitorEnter =
+          shouldSynthesizeMonitorEnterExit && !target.isStatic();
+      if (shouldSynthesizeNullCheckForReceiver
+          && !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
         List<Value> arguments = code.collectArguments();
         if (!arguments.isEmpty()) {
           Value receiver = arguments.get(0);
@@ -617,9 +640,119 @@
           assert false : "Unable to synthesize a null check for the receiver";
         }
       }
+
+      // Insert monitor-enter and monitor-exit instructions if the method is synchronized.
+      if (shouldSynthesizeMonitorEnterExit) {
+        TypeLatticeElement throwableType =
+            TypeLatticeElement.fromDexType(
+                dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView);
+
+        code.prepareBlocksForCatchHandlers();
+
+        int nextBlockNumber = code.getHighestBlockNumber() + 1;
+
+        // Create a block for holding the monitor-exit instruction.
+        BasicBlock monitorExitBlock = new BasicBlock();
+        monitorExitBlock.setNumber(nextBlockNumber++);
+
+        // For each block in the code that may throw, add a catch-all handler targeting the
+        // monitor-exit block.
+        List<BasicBlock> moveExceptionBlocks = new ArrayList<>();
+        for (BasicBlock block : code.blocks) {
+          if (!block.canThrow()) {
+            continue;
+          }
+          if (block.hasCatchHandlers()
+              && block.getCatchHandlersWithSuccessorIndexes().hasCatchAll(dexItemFactory)) {
+            continue;
+          }
+          BasicBlock moveExceptionBlock =
+              BasicBlock.createGotoBlock(
+                  nextBlockNumber++, Position.none(), code.metadata(), monitorExitBlock);
+          InstructionListIterator moveExceptionBlockIterator =
+              moveExceptionBlock.listIterator(code);
+          moveExceptionBlockIterator.setInsertionPosition(Position.syntheticNone());
+          moveExceptionBlockIterator.add(
+              new MoveException(
+                  code.createValue(throwableType), dexItemFactory.throwableType, options));
+          block.appendCatchHandler(moveExceptionBlock, dexItemFactory.throwableType);
+          moveExceptionBlocks.add(moveExceptionBlock);
+        }
+
+        // Create a phi for the exception values such that we can rethrow the exception if needed.
+        Value exceptionValue;
+        if (moveExceptionBlocks.size() == 1) {
+          exceptionValue =
+              ListUtils.first(moveExceptionBlocks).getInstructions().getFirst().outValue();
+        } else {
+          Phi phi = code.createPhi(monitorExitBlock, throwableType);
+          List<Value> operands =
+              ListUtils.map(
+                  moveExceptionBlocks, block -> block.getInstructions().getFirst().outValue());
+          phi.addOperands(operands);
+          exceptionValue = phi;
+        }
+
+        InstructionListIterator monitorExitBlockIterator = monitorExitBlock.listIterator(code);
+        monitorExitBlockIterator.setInsertionPosition(Position.syntheticNone());
+        monitorExitBlockIterator.add(new Throw(exceptionValue));
+        monitorExitBlock.getMutablePredecessors().addAll(moveExceptionBlocks);
+
+        // Insert the newly created blocks.
+        code.blocks.addAll(moveExceptionBlocks);
+        code.blocks.add(monitorExitBlock);
+
+        // Create a block for holding the monitor-enter instruction. Note that, since this block
+        // is created after we attach catch-all handlers to the code, this block will not have any
+        // catch handlers.
+        BasicBlock entryBlock = code.entryBlock();
+        InstructionListIterator entryBlockIterator = entryBlock.listIterator(code);
+        entryBlockIterator.nextUntil(not(Instruction::isArgument));
+        entryBlockIterator.previous();
+        BasicBlock monitorEnterBlock = entryBlockIterator.split(code, 0, null);
+        assert !monitorEnterBlock.hasCatchHandlers();
+
+        InstructionListIterator monitorEnterBlockIterator = monitorEnterBlock.listIterator(code);
+        monitorEnterBlockIterator.setInsertionPosition(Position.syntheticNone());
+
+        // If this is a static method, then the class object will act as the lock, so we load it
+        // using a const-class instruction.
+        Value lockValue;
+        if (target.isStatic()) {
+          lockValue =
+              code.createValue(
+                  TypeLatticeElement.fromDexType(
+                      dexItemFactory.objectType, definitelyNotNull(), appView));
+          monitorEnterBlockIterator.add(new ConstClass(lockValue, target.method.holder));
+        } else {
+          lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
+        }
+
+        // Insert the monitor-enter and monitor-exit instructions.
+        monitorEnterBlockIterator.add(new Monitor(Monitor.Type.ENTER, lockValue));
+        monitorExitBlockIterator.previous();
+        monitorExitBlockIterator.add(new Monitor(Monitor.Type.EXIT, lockValue));
+        monitorExitBlock.close(null);
+
+        for (BasicBlock block : code.blocks) {
+          if (block.exit().isReturn()) {
+            // Since return instructions are not allowed after a throwing instruction in a block
+            // with catch handlers, the call to prepareBlocksForCatchHandlers() has already taken
+            // care of ensuring that all return blocks have no throwing instructions.
+            assert !block.canThrow();
+
+            InstructionListIterator instructionIterator =
+                block.listIterator(code, block.getInstructions().size() - 1);
+            instructionIterator.setInsertionPosition(Position.syntheticNone());
+            instructionIterator.add(new Monitor(Monitor.Type.EXIT, lockValue));
+          }
+        }
+      }
+
       if (!target.isProcessed()) {
         lensCodeRewriter.rewrite(code, target);
       }
+      assert code.isConsistentSSA();
       return new InlineeWithReason(code, reason);
     }
   }
@@ -778,7 +911,8 @@
                   appView,
                   getPositionForInlining(invoke, context),
                   lensCodeRewriter);
-          if (strategy.willExceedBudget(inlinee, block, whyAreYouNotInliningReporter)) {
+          if (strategy.willExceedBudget(
+              code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 074160b..f1256d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -208,8 +208,7 @@
   }
 
   public ConstraintWithTarget forMonitor() {
-    // Conservative choice.
-    return ConstraintWithTarget.NEVER;
+    return ConstraintWithTarget.ALWAYS;
   }
 
   public ConstraintWithTarget forMove() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 21b060a..5f9bb2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -30,6 +30,8 @@
    * <p>Return true if the strategy will *not* allow inlining.
    */
   boolean willExceedBudget(
+      IRCode code,
+      InvokeMethod invoke,
       InlineeWithReason inlinee,
       BasicBlock block,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 0adf3f7..cea57a8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -31,14 +30,11 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
-import java.util.ArrayDeque;
 import java.util.BitSet;
-import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -413,107 +409,4 @@
         && type.asReferenceTypeLatticeElement().isNullable()
         && value.numberOfAllUsers() > 0;
   }
-
-  public void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
-    Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
-    normalExits.addAll(code.computeNormalExitBlocks());
-    DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
-    List<Value> arguments = code.collectArguments();
-    BitSet facts = new BitSet();
-    Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
-    for (int index = 0; index < arguments.size(); index++) {
-      Value argument = arguments.get(index);
-      // Consider reference-type parameter only.
-      if (!argument.getTypeLattice().isReference()) {
-        continue;
-      }
-      // The receiver is always non-null on normal exits.
-      if (argument.isThis()) {
-        facts.set(index);
-        continue;
-      }
-      // Collect basic blocks that check nullability of the parameter.
-      nullCheckedBlocks.clear();
-      for (Instruction user : argument.uniqueUsers()) {
-        if (user.isAssumeNonNull()) {
-          nullCheckedBlocks.add(user.asAssumeNonNull().getBlock());
-        }
-        if (user.isIf()
-            && user.asIf().isZeroTest()
-            && (user.asIf().getType() == Type.EQ || user.asIf().getType() == Type.NE)) {
-          nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
-        }
-      }
-      if (!nullCheckedBlocks.isEmpty()) {
-        boolean allExitsCovered = true;
-        for (BasicBlock normalExit : normalExits) {
-          if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
-            allExitsCovered = false;
-            break;
-          }
-        }
-        if (allExitsCovered) {
-          facts.set(index);
-        }
-      }
-    }
-    if (facts.length() > 0) {
-      feedback.setNonNullParamOnNormalExits(code.method, facts);
-    }
-  }
-
-  private boolean isNormalExitDominated(
-      BasicBlock normalExit,
-      IRCode code,
-      DominatorTree dominatorTree,
-      Set<BasicBlock> nullCheckedBlocks) {
-    // Each normal exit should be...
-    for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
-      // A) ...directly dominated by any null-checked block.
-      if (dominatorTree.dominatedBy(normalExit, nullCheckedBlock)) {
-        return true;
-      }
-    }
-    // B) ...or indirectly dominated by null-checked blocks.
-    // Although the normal exit is not dominated by any of null-checked blocks (because of other
-    // paths to the exit), it could be still the case that all possible paths to that exit should
-    // pass some of null-checked blocks.
-    Set<BasicBlock> visited = Sets.newIdentityHashSet();
-    // Initial fan-out of predecessors.
-    Deque<BasicBlock> uncoveredPaths = new ArrayDeque<>(normalExit.getPredecessors());
-    while (!uncoveredPaths.isEmpty()) {
-      BasicBlock uncoveredPath = uncoveredPaths.poll();
-      // Stop traversing upwards if we hit the entry block: if the entry block has an non-null,
-      // this case should be handled already by A) because the entry block surely dominates all
-      // normal exits.
-      if (uncoveredPath == code.entryBlock()) {
-        return false;
-      }
-      // Make sure we're not visiting the same block over and over again.
-      if (!visited.add(uncoveredPath)) {
-        // But, if that block is the last one in the queue, the normal exit is not fully covered.
-        if (uncoveredPaths.isEmpty()) {
-          return false;
-        } else {
-          continue;
-        }
-      }
-      boolean pathCovered = false;
-      for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
-        if (dominatorTree.dominatedBy(uncoveredPath, nullCheckedBlock)) {
-          pathCovered = true;
-          break;
-        }
-      }
-      if (!pathCovered) {
-        // Fan out predecessors one more level.
-        // Note that remaining, unmatched null-checked blocks should cover newly added paths.
-        uncoveredPaths.addAll(uncoveredPath.getPredecessors());
-      }
-    }
-    // Reaching here means that every path to the given normal exit is covered by the set of
-    // null-checked blocks.
-    assert uncoveredPaths.isEmpty();
-    return true;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index f836195..809db6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -494,6 +494,13 @@
         continue;
       }
 
+      if (user.isMonitor()) {
+        // Since this instance never escapes and is guaranteed to be non-null, any monitor
+        // instructions are no-ops.
+        removeInstruction(user);
+        continue;
+      }
+
       throw new Unreachable(
           "Unexpected usage left in method `"
               + method.method.toSourceString()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 3279d51..9283234 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -37,8 +37,8 @@
   static ParameterUsagesInfo UNKNOWN_PARAMETER_USAGE_INFO = null;
   static boolean UNKNOWN_MAY_HAVE_SIDE_EFFECTS = true;
   static boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false;
-  private static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
-  private static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
+  static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
+  static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
 
   private DefaultMethodOptimizationInfo() {}
 
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
new file mode 100644
index 0000000..78369e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -0,0 +1,948 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.info;
+
+import static com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query.DIRECTLY;
+import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
+
+import com.android.tools.r8.graph.AppView;
+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.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.DeterminismAnalysis;
+import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
+import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
+import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+public class MethodOptimizationInfoCollector {
+  private final AppView<?> appView;
+  private final InternalOptions options;
+  private final DexItemFactory dexItemFactory;
+
+  public MethodOptimizationInfoCollector(AppView<?> appView) {
+    this.appView = appView;
+    this.options = appView.options();
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  // TODO(b/141656615): Use this and then make all utils in this collector `private`.
+  public void collectMethodOptimizationInfo(
+      DexEncodedMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
+      DynamicTypeOptimization dynamicTypeOptimization) {
+    identifyClassInlinerEligibility(method, code, feedback);
+    identifyParameterUsages(method, code, feedback);
+    identifyReturnsArgument(method, code, feedback);
+    identifyTrivialInitializer(method, code, feedback);
+    if (options.enableInlining) {
+      identifyInvokeSemanticsForInlining(method, code, appView, feedback);
+    }
+    computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
+    computeInitializedClassesOnNormalExit(feedback, method, code);
+    computeMayHaveSideEffects(feedback, method, code);
+    computeReturnValueOnlyDependsOnArguments(feedback, method, code);
+    computeNonNullParamOrThrow(feedback, method, code);
+    computeNonNullParamOnNormalExits(feedback, code);
+  }
+
+  public void identifyClassInlinerEligibility(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    // Method eligibility is calculated in similar way for regular method
+    // and for the constructor. To be eligible method should only be using its
+    // receiver in the following ways:
+    //
+    //  (1) as a receiver of reads/writes of instance fields of the holder class,
+    //  (2) as a return value,
+    //  (3) as a receiver of a call to the superclass initializer. Note that we don't
+    //      check what is passed to superclass initializer as arguments, only check
+    //      that it is not the instance being initialized,
+    //  (4) as argument to a monitor instruction.
+    //
+    // Note that (4) can safely be removed as the receiver is guaranteed not to escape when we class
+    // inline it, and hence any monitor instructions are no-ops.
+    boolean instanceInitializer = method.isInstanceInitializer();
+    if (method.accessFlags.isNative()
+        || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)) {
+      return;
+    }
+
+    feedback.setClassInlinerEligibility(method, null);  // To allow returns below.
+
+    Value receiver = code.getThis();
+    if (receiver.numberOfPhiUsers() > 0) {
+      return;
+    }
+
+    DexClass clazz = appView.definitionFor(method.method.holder);
+    if (clazz == null) {
+      return;
+    }
+
+    boolean receiverUsedAsReturnValue = false;
+    boolean seenSuperInitCall = false;
+    for (Instruction insn : receiver.uniqueUsers()) {
+      if (insn.isMonitor()) {
+        continue;
+      }
+
+      if (insn.isReturn()) {
+        receiverUsedAsReturnValue = true;
+        continue;
+      }
+
+      if (insn.isInstanceGet() || insn.isInstancePut()) {
+        if (insn.isInstancePut()) {
+          InstancePut instancePutInstruction = insn.asInstancePut();
+          // Only allow field writes to the receiver.
+          if (instancePutInstruction.object() != receiver) {
+            return;
+          }
+          // Do not allow the receiver to escape via a field write.
+          if (instancePutInstruction.value() == receiver) {
+            return;
+          }
+        }
+        DexField field = insn.asFieldInstruction().getField();
+        if (field.holder == clazz.type && clazz.lookupInstanceField(field) != null) {
+          // Require only accessing instance fields of the *current* class.
+          continue;
+        }
+        return;
+      }
+
+      // If this is an instance initializer allow one call to superclass instance initializer.
+      if (insn.isInvokeDirect()) {
+        InvokeDirect invokedDirect = insn.asInvokeDirect();
+        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
+        if (dexItemFactory.isConstructor(invokedMethod)
+            && invokedMethod.holder == clazz.superType
+            && invokedDirect.inValues().lastIndexOf(receiver) == 0
+            && !seenSuperInitCall
+            && instanceInitializer) {
+          seenSuperInitCall = true;
+          continue;
+        }
+        // We don't support other direct calls yet.
+        return;
+      }
+
+      // Other receiver usages make the method not eligible.
+      return;
+    }
+    if (instanceInitializer && !seenSuperInitCall) {
+      // Call to super constructor not found?
+      return;
+    }
+
+    feedback.setClassInlinerEligibility(
+        method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
+  }
+
+  public void identifyParameterUsages(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    List<ParameterUsage> usages = new ArrayList<>();
+    List<Value> values = code.collectArguments();
+    for (int i = 0; i < values.size(); i++) {
+      Value value = values.get(i);
+      if (value.numberOfPhiUsers() > 0) {
+        continue;
+      }
+      ParameterUsage usage = collectParameterUsages(i, value);
+      if (usage != null) {
+        usages.add(usage);
+      }
+    }
+    feedback.setParameterUsages(method, usages.isEmpty() ? null : new ParameterUsagesInfo(usages));
+  }
+
+  private ParameterUsage collectParameterUsages(int i, Value value) {
+    ParameterUsageBuilder builder = new ParameterUsageBuilder(value, i);
+    for (Instruction user : value.uniqueUsers()) {
+      if (!builder.note(user)) {
+        return null;
+      }
+    }
+    return builder.build();
+  }
+
+  public void identifyReturnsArgument(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    List<BasicBlock> normalExits = code.computeNormalExitBlocks();
+    if (normalExits.isEmpty()) {
+      feedback.methodNeverReturnsNormally(method);
+      return;
+    }
+    Return firstExit = normalExits.get(0).exit().asReturn();
+    if (firstExit.isReturnVoid()) {
+      return;
+    }
+    Value returnValue = firstExit.returnValue();
+    boolean isNeverNull = returnValue.getTypeLattice().isReference() && returnValue.isNeverNull();
+    for (int i = 1; i < normalExits.size(); i++) {
+      Return exit = normalExits.get(i).exit().asReturn();
+      Value value = exit.returnValue();
+      if (value != returnValue) {
+        returnValue = null;
+      }
+      isNeverNull &= value.getTypeLattice().isReference() && value.isNeverNull();
+    }
+    if (returnValue != null) {
+      if (returnValue.isArgument()) {
+        // Find the argument number.
+        int index = code.collectArguments().indexOf(returnValue);
+        assert index != -1;
+        feedback.methodReturnsArgument(method, index);
+      }
+      if (returnValue.isConstant()) {
+        if (returnValue.definition.isConstNumber()) {
+          long value = returnValue.definition.asConstNumber().getRawValue();
+          feedback.methodReturnsConstantNumber(method, value);
+        } else if (returnValue.definition.isConstString()) {
+          ConstString constStringInstruction = returnValue.definition.asConstString();
+          if (!constStringInstruction.instructionInstanceCanThrow()) {
+            feedback.methodReturnsConstantString(method, constStringInstruction.getValue());
+          }
+        }
+      }
+    }
+    if (isNeverNull) {
+      feedback.methodNeverReturnsNull(method);
+    }
+  }
+
+  public void identifyTrivialInitializer(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    if (!method.isInstanceInitializer() && !method.isClassInitializer()) {
+      return;
+    }
+
+    if (method.accessFlags.isNative()) {
+      return;
+    }
+
+    DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
+    if (clazz == null) {
+      return;
+    }
+
+    feedback.setTrivialInitializer(
+        method,
+        method.isInstanceInitializer()
+            ? computeInstanceInitializerInfo(code, clazz, appView::definitionFor)
+            : computeClassInitializerInfo(code, clazz));
+  }
+
+  // This method defines trivial class initializer as follows:
+  //
+  // ** The initializer may only instantiate an instance of the same class,
+  //    initialize it with a call to a trivial constructor *without* arguments,
+  //    and assign this instance to a static final field of the same class.
+  //
+  private synchronized TrivialInitializer computeClassInitializerInfo(IRCode code, DexClass clazz) {
+    Value createdSingletonInstance = null;
+    DexField singletonField = null;
+    for (Instruction insn : code.instructions()) {
+      if (insn.isConstNumber()) {
+        continue;
+      }
+
+      if (insn.isConstString()) {
+        if (insn.instructionInstanceCanThrow()) {
+          return null;
+        }
+        continue;
+      }
+
+      if (insn.isReturn()) {
+        continue;
+      }
+
+      if (insn.isNewInstance()) {
+        NewInstance newInstance = insn.asNewInstance();
+        if (createdSingletonInstance != null
+            || newInstance.clazz != clazz.type
+            || insn.outValue() == null) {
+          return null;
+        }
+        createdSingletonInstance = insn.outValue();
+        continue;
+      }
+
+      if (insn.isInvokeDirect()) {
+        InvokeDirect invokedDirect = insn.asInvokeDirect();
+        if (createdSingletonInstance == null
+            || invokedDirect.getReceiver() != createdSingletonInstance) {
+          return null;
+        }
+        DexEncodedMethod callTarget = clazz.lookupDirectMethod(invokedDirect.getInvokedMethod());
+        if (callTarget == null
+            || !callTarget.isInstanceInitializer()
+            || !callTarget.method.proto.parameters.isEmpty()
+            || callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null) {
+          return null;
+        }
+        continue;
+      }
+
+      if (insn.isStaticPut()) {
+        StaticPut staticPut = insn.asStaticPut();
+        if (singletonField != null
+            || createdSingletonInstance == null
+            || staticPut.value() != createdSingletonInstance) {
+          return null;
+        }
+        DexEncodedField field = clazz.lookupStaticField(staticPut.getField());
+        if (field == null
+            || !field.accessFlags.isStatic()
+            || !field.accessFlags.isFinal()) {
+          return null;
+        }
+        singletonField = field.field;
+        continue;
+      }
+
+      // Other instructions make the class initializer not eligible.
+      return null;
+    }
+    return singletonField == null ? null : new TrivialClassInitializer(singletonField);
+  }
+
+  // This method defines trivial instance initializer as follows:
+  //
+  // ** The initializer may call the initializer of the base class, which
+  //    itself must be trivial.
+  //
+  // ** java.lang.Object.<init>() is considered trivial.
+  //
+  // ** all arguments passed to a super-class initializer must be non-throwing
+  //    constants or arguments.
+  //
+  // ** Assigns arguments or non-throwing constants to fields of this class.
+  //
+  // (Note that this initializer does not have to have zero arguments.)
+  private TrivialInitializer computeInstanceInitializerInfo(
+      IRCode code, DexClass clazz, Function<DexType, DexClass> typeToClass) {
+    Value receiver = code.getThis();
+    for (Instruction insn : code.instructions()) {
+      if (insn.isReturn()) {
+        continue;
+      }
+
+      if (insn.isArgument()) {
+        continue;
+      }
+
+      if (insn.isConstInstruction()) {
+        if (insn.instructionInstanceCanThrow()) {
+          return null;
+        } else {
+          continue;
+        }
+      }
+
+      if (insn.isInvokeDirect()) {
+        InvokeDirect invokedDirect = insn.asInvokeDirect();
+        DexMethod invokedMethod = invokedDirect.getInvokedMethod();
+        if (invokedMethod.holder != clazz.superType) {
+          return null;
+        }
+        // java.lang.Object.<init>() is considered trivial.
+        if (invokedMethod == dexItemFactory.objectMethods.constructor) {
+          continue;
+        }
+        DexClass holder = typeToClass.apply(invokedMethod.holder);
+        if (holder == null) {
+          return null;
+        }
+        DexEncodedMethod callTarget = holder.lookupDirectMethod(invokedMethod);
+        if (callTarget == null
+            || !callTarget.isInstanceInitializer()
+            || callTarget.getOptimizationInfo().getTrivialInitializerInfo() == null
+            || invokedDirect.getReceiver() != receiver) {
+          return null;
+        }
+        for (Value value : invokedDirect.inValues()) {
+          if (value != receiver && !(value.isConstant() || value.isArgument())) {
+            return null;
+          }
+        }
+        continue;
+      }
+
+      if (insn.isInstancePut()) {
+        InstancePut instancePut = insn.asInstancePut();
+        DexEncodedField field = clazz.lookupInstanceField(instancePut.getField());
+        if (field == null
+            || instancePut.object() != receiver
+            || (instancePut.value() != receiver && !instancePut.value().isArgument())) {
+          return null;
+        }
+        continue;
+      }
+
+      if (insn.isGoto()) {
+        // Trivial goto to the next block.
+        if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
+          continue;
+        }
+        return null;
+      }
+
+      // Other instructions make the instance initializer not eligible.
+      return null;
+    }
+    return TrivialInstanceInitializer.INSTANCE;
+  }
+
+  public void identifyInvokeSemanticsForInlining(
+      DexEncodedMethod method, IRCode code, AppView<?> appView, OptimizationFeedback feedback) {
+    if (method.isStatic()) {
+      // Identifies if the method preserves class initialization after inlining.
+      feedback.markTriggerClassInitBeforeAnySideEffect(
+          method, triggersClassInitializationBeforeSideEffect(method.method.holder, code, appView));
+    } else {
+      // Identifies if the method preserves null check of the receiver after inlining.
+      final Value receiver = code.getThis();
+      feedback.markCheckNullReceiverBeforeAnySideEffect(
+          method, receiver.isUsed() && checksNullBeforeSideEffect(code, receiver, appView));
+    }
+  }
+
+  /**
+   * Returns true if the given code unconditionally triggers class initialization before any other
+   * side effecting instruction.
+   *
+   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
+   */
+  private static boolean triggersClassInitializationBeforeSideEffect(
+      DexType clazz, IRCode code, AppView<?> appView) {
+    return alwaysTriggerExpectedEffectBeforeAnythingElse(
+        code,
+        (instruction, it) -> {
+          DexType context = code.method.method.holder;
+          if (instruction.definitelyTriggersClassInitialization(
+              clazz, context, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
+            // In order to preserve class initialization semantic, the exception must not be caught
+            // by any handler. Therefore, we must ignore this instruction if it is covered by a
+            // catch handler.
+            // Note: this is a conservative approach where we consider that any catch handler could
+            // catch the exception, even if it cannot catch an ExceptionInInitializerError.
+            if (!instruction.getBlock().hasCatchHandlers()) {
+              // We found an instruction that preserves initialization of the class.
+              return InstructionEffect.DESIRED_EFFECT;
+            }
+          } else if (instruction.instructionMayHaveSideEffects(appView, clazz)) {
+            // We found a side effect before class initialization.
+            return InstructionEffect.OTHER_EFFECT;
+          }
+          return InstructionEffect.NO_EFFECT;
+        });
+  }
+
+  /**
+   * Returns true if the given code unconditionally triggers an expected effect before anything
+   * else, false otherwise.
+   *
+   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
+   */
+  private static boolean alwaysTriggerExpectedEffectBeforeAnythingElse(
+      IRCode code, BiFunction<Instruction, InstructionIterator, InstructionEffect> function) {
+    final int color = code.reserveMarkingColor();
+    try {
+      ArrayDeque<BasicBlock> worklist = new ArrayDeque<>();
+      final BasicBlock entry = code.entryBlock();
+      worklist.add(entry);
+      entry.mark(color);
+
+      while (!worklist.isEmpty()) {
+        BasicBlock currentBlock = worklist.poll();
+        assert currentBlock.isMarked(color);
+
+        InstructionEffect result = InstructionEffect.NO_EFFECT;
+        InstructionIterator it = currentBlock.iterator();
+        while (result == InstructionEffect.NO_EFFECT && it.hasNext()) {
+          result = function.apply(it.next(), it);
+        }
+        if (result == InstructionEffect.OTHER_EFFECT) {
+          // We found an instruction that is causing an unexpected side effect.
+          return false;
+        } else if (result == InstructionEffect.DESIRED_EFFECT) {
+          // The current path is causing the expected effect. No need to go deeper in this path,
+          // go to the next block in the work list.
+          continue;
+        } else if (result == InstructionEffect.CONDITIONAL_EFFECT) {
+          assert !currentBlock.getNormalSuccessors().isEmpty();
+          Instruction lastInstruction = currentBlock.getInstructions().getLast();
+          assert lastInstruction.isIf();
+          // The current path is checking if the value of interest is null. Go deeper into the path
+          // that corresponds to the null value.
+          BasicBlock targetIfReceiverIsNull = lastInstruction.asIf().targetFromCondition(0);
+          if (!targetIfReceiverIsNull.isMarked(color)) {
+            worklist.add(targetIfReceiverIsNull);
+            targetIfReceiverIsNull.mark(color);
+          }
+        } else {
+          assert result == InstructionEffect.NO_EFFECT;
+          // The block did not cause any particular effect.
+          if (currentBlock.getNormalSuccessors().isEmpty()) {
+            // This is the end of the current non-exceptional path and we did not find any expected
+            // effect. It means there is at least one path where the expected effect does not
+            // happen.
+            Instruction lastInstruction = currentBlock.getInstructions().getLast();
+            assert lastInstruction.isReturn() || lastInstruction.isThrow();
+            return false;
+          } else {
+            // Look into successors
+            for (BasicBlock successor : currentBlock.getSuccessors()) {
+              if (!successor.isMarked(color)) {
+                worklist.add(successor);
+                successor.mark(color);
+              }
+            }
+          }
+        }
+      }
+      // If we reach this point, we checked that the expected effect happens in every possible path.
+      return true;
+    } finally {
+      code.returnMarkingColor(color);
+    }
+  }
+
+  /**
+   * Returns true if the given code unconditionally throws if value is null before any other side
+   * effect instruction.
+   *
+   * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
+   */
+  private static boolean checksNullBeforeSideEffect(IRCode code, Value value, AppView<?> appView) {
+    return alwaysTriggerExpectedEffectBeforeAnythingElse(
+        code,
+        (instr, it) -> {
+          BasicBlock currentBlock = instr.getBlock();
+          // If the code explicitly checks the nullability of the value, we should visit the next
+          // block that corresponds to the null value where NPE semantic could be preserved.
+          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.
+              for (BasicBlock predecessor : currentBlock.getPredecessors()) {
+                if (isNullCheck(predecessor.exit(), value)) {
+                  return InstructionEffect.DESIRED_EFFECT;
+                }
+              }
+              // 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())) {
+            it.next(); // Skip call to NullPointerException.<init>.
+            return InstructionEffect.NO_EFFECT;
+          } else if (instr.throwsNpeIfValueIsNull(value, appView.dexItemFactory())) {
+            // In order to preserve NPE semantic, the exception must not be caught by any handler.
+            // Therefore, we must ignore this instruction if it is covered by a catch handler.
+            // Note: this is a conservative approach where we consider that any catch handler could
+            // catch the exception, even if it cannot catch a NullPointerException.
+            if (!currentBlock.hasCatchHandlers()) {
+              // We found a NPE check on the value.
+              return InstructionEffect.DESIRED_EFFECT;
+            }
+          } else if (instr.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
+            // If the current instruction is const-string, this could load the parameter name.
+            // Just make sure it is indeed not throwing.
+            if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
+              return InstructionEffect.NO_EFFECT;
+            }
+            // We found a side effect before a NPE check.
+            return InstructionEffect.OTHER_EFFECT;
+          }
+          return InstructionEffect.NO_EFFECT;
+        });
+  }
+
+  /**
+   * An enum used to classify instructions according to a particular effect that they produce.
+   *
+   * The "effect" of an instruction can be seen as a program state change (or semantic change) at
+   * runtime execution. For example, an instruction could cause the initialization of a class,
+   * change the value of a field, ... while other instructions do not.
+   *
+   * This classification also depends on the type of analysis that is using it. For instance, an
+   * analysis can look for instructions that cause class initialization while another look for
+   * instructions that check nullness of a particular object.
+   *
+   * On the other hand, some instructions may provide a non desired effect which is a signal for
+   * the analysis to stop.
+   */
+  private enum InstructionEffect {
+    DESIRED_EFFECT,
+    CONDITIONAL_EFFECT,
+    OTHER_EFFECT,
+    NO_EFFECT
+  }
+
+  // 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) {
+    if (!instr.isInvokeStatic()) {
+      return false;
+    }
+    // We need to strip 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.graphLense().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;
+  }
+
+  private static boolean isNullCheck(Instruction instr, Value value) {
+    return instr.isIf() && instr.asIf().isZeroTest()
+        && instr.inValues().get(0).equals(value)
+        && (instr.asIf().getType() == If.Type.EQ || instr.asIf().getType() == If.Type.NE);
+  }
+
+  /**
+   * Returns true if the given instruction is {@code v <- new-instance NullPointerException}, and
+   * the next instruction is {@code invoke-direct v, NullPointerException.<init>()}.
+   */
+  private static boolean isInstantiationOfNullPointerException(
+      Instruction instruction, InstructionIterator it, DexItemFactory dexItemFactory) {
+    if (!instruction.isNewInstance()
+        || instruction.asNewInstance().clazz != dexItemFactory.npeType) {
+      return false;
+    }
+    Instruction next = it.peekNext();
+    if (next == null
+        || !next.isInvokeDirect()
+        || next.asInvokeDirect().getInvokedMethod() != dexItemFactory.npeMethods.init) {
+      return false;
+    }
+    return true;
+  }
+
+  public void computeDynamicReturnType(
+      DynamicTypeOptimization dynamicTypeOptimization,
+      OptimizationFeedback feedback,
+      DexEncodedMethod method,
+      IRCode code) {
+    if (dynamicTypeOptimization != null) {
+      DexType staticReturnTypeRaw = method.method.proto.returnType;
+      if (!staticReturnTypeRaw.isReferenceType()) {
+        return;
+      }
+      TypeLatticeElement dynamicReturnType =
+          dynamicTypeOptimization.computeDynamicReturnType(method, code);
+      if (dynamicReturnType != null) {
+        TypeLatticeElement staticReturnType =
+            TypeLatticeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
+        // If the dynamic return type is not more precise than the static return type there is no
+        // need to record it.
+        if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) {
+          feedback.methodReturnsObjectOfType(method, appView, dynamicReturnType);
+        }
+      }
+      ClassTypeLatticeElement exactReturnType =
+          dynamicTypeOptimization.computeDynamicLowerBoundType(method, code);
+      if (exactReturnType != null) {
+        feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType);
+      }
+    }
+  }
+
+  public void computeInitializedClassesOnNormalExit(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+    if (options.enableInitializedClassesAnalysis && appView.appInfo().hasLiveness()) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+      Set<DexType> initializedClasses =
+          InitializedClassesOnNormalExitAnalysis.computeInitializedClassesOnNormalExit(
+              appViewWithLiveness, code);
+      if (initializedClasses != null && !initializedClasses.isEmpty()) {
+        feedback.methodInitializesClassesOnNormalExit(method, initializedClasses);
+      }
+    }
+  }
+
+  public void computeMayHaveSideEffects(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+    // If the method is native, we don't know what could happen.
+    assert !method.accessFlags.isNative();
+    if (!options.enableSideEffectAnalysis) {
+      return;
+    }
+    if (appView.appInfo().withLiveness().mayHaveSideEffects.containsKey(method.method)) {
+      return;
+    }
+    DexType context = method.method.holder;
+    if (method.isClassInitializer()) {
+      // For class initializers, we also wish to compute if the class initializer has observable
+      // side effects.
+      ClassInitializerSideEffect classInitializerSideEffect =
+          ClassInitializerSideEffectAnalysis.classInitializerCanBePostponed(appView, code);
+      if (classInitializerSideEffect.isNone()) {
+        feedback.methodMayNotHaveSideEffects(method);
+        feedback.classInitializerMayBePostponed(method);
+      } else if (classInitializerSideEffect.canBePostponed()) {
+        feedback.classInitializerMayBePostponed(method);
+      }
+      return;
+    }
+    boolean mayHaveSideEffects;
+    if (method.accessFlags.isSynchronized()) {
+      // If the method is synchronized then it acquires a lock.
+      mayHaveSideEffects = true;
+    } else if (method.isInstanceInitializer() && hasNonTrivialFinalizeMethod(context)) {
+      // If a class T overrides java.lang.Object.finalize(), then treat the constructor as having
+      // side effects. This ensures that we won't remove instructions on the form `new-instance
+      // {v0}, T`.
+      mayHaveSideEffects = true;
+    } else {
+      // Otherwise, check if there is an instruction that has side effects.
+      mayHaveSideEffects =
+          Streams.stream(code.instructions())
+              .anyMatch(instruction -> instruction.instructionMayHaveSideEffects(appView, context));
+    }
+    if (!mayHaveSideEffects) {
+      feedback.methodMayNotHaveSideEffects(method);
+    }
+  }
+
+  // Returns true if `method` is an initializer and the enclosing class overrides the method
+  // `void java.lang.Object.finalize()`.
+  private boolean hasNonTrivialFinalizeMethod(DexType type) {
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz != null) {
+      if (clazz.isProgramClass() && !clazz.isInterface()) {
+        ResolutionResult resolutionResult =
+            appView
+                .appInfo()
+                .resolveMethodOnClass(clazz, appView.dexItemFactory().objectMethods.finalize);
+        for (DexEncodedMethod target : resolutionResult.asListOfTargets()) {
+          if (target.method != appView.dexItemFactory().objectMethods.finalize) {
+            return true;
+          }
+        }
+        return false;
+      } else {
+        // Conservatively report that the library class could implement finalize().
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public void computeReturnValueOnlyDependsOnArguments(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+    if (!options.enableDeterminismAnalysis) {
+      return;
+    }
+    boolean returnValueOnlyDependsOnArguments =
+        DeterminismAnalysis.returnValueOnlyDependsOnArguments(appView.withLiveness(), code);
+    if (returnValueOnlyDependsOnArguments) {
+      feedback.methodReturnValueOnlyDependsOnArguments(method);
+    }
+  }
+
+  // Track usage of parameters and compute their nullability and possibility of NPE.
+  public void computeNonNullParamOrThrow(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+    if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
+      return;
+    }
+    List<Value> arguments = code.collectArguments();
+    BitSet paramsCheckedForNull = new BitSet();
+    for (int index = 0; index < arguments.size(); index++) {
+      Value argument = arguments.get(index);
+      // This handles cases where the parameter is checked via Kotlin Intrinsics:
+      //
+      //   kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
+      //
+      // or its inlined version:
+      //
+      //   if (param != null) return;
+      //   invoke-static throwParameterIsNullException(msg)
+      //
+      // or some other variants, e.g., throw null or NPE after the direct null check.
+      if (argument.isUsed() && checksNullBeforeSideEffect(code, argument, appView)) {
+        paramsCheckedForNull.set(index);
+      }
+    }
+    if (paramsCheckedForNull.length() > 0) {
+      feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
+    }
+  }
+
+  public void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
+    Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
+    normalExits.addAll(code.computeNormalExitBlocks());
+    DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+    List<Value> arguments = code.collectArguments();
+    BitSet facts = new BitSet();
+    Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+    for (int index = 0; index < arguments.size(); index++) {
+      Value argument = arguments.get(index);
+      // Consider reference-type parameter only.
+      if (!argument.getTypeLattice().isReference()) {
+        continue;
+      }
+      // The receiver is always non-null on normal exits.
+      if (argument.isThis()) {
+        facts.set(index);
+        continue;
+      }
+      // Collect basic blocks that check nullability of the parameter.
+      nullCheckedBlocks.clear();
+      for (Instruction user : argument.uniqueUsers()) {
+        if (user.isAssumeNonNull()) {
+          nullCheckedBlocks.add(user.asAssumeNonNull().getBlock());
+        }
+        if (user.isIf()
+            && user.asIf().isZeroTest()
+            && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
+          nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
+        }
+      }
+      if (!nullCheckedBlocks.isEmpty()) {
+        boolean allExitsCovered = true;
+        for (BasicBlock normalExit : normalExits) {
+          if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
+            allExitsCovered = false;
+            break;
+          }
+        }
+        if (allExitsCovered) {
+          facts.set(index);
+        }
+      }
+    }
+    if (facts.length() > 0) {
+      feedback.setNonNullParamOnNormalExits(code.method, facts);
+    }
+  }
+
+  private boolean isNormalExitDominated(
+      BasicBlock normalExit,
+      IRCode code,
+      DominatorTree dominatorTree,
+      Set<BasicBlock> nullCheckedBlocks) {
+    // Each normal exit should be...
+    for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
+      // A) ...directly dominated by any null-checked block.
+      if (dominatorTree.dominatedBy(normalExit, nullCheckedBlock)) {
+        return true;
+      }
+    }
+    // B) ...or indirectly dominated by null-checked blocks.
+    // Although the normal exit is not dominated by any of null-checked blocks (because of other
+    // paths to the exit), it could be still the case that all possible paths to that exit should
+    // pass some of null-checked blocks.
+    Set<BasicBlock> visited = Sets.newIdentityHashSet();
+    // Initial fan-out of predecessors.
+    Deque<BasicBlock> uncoveredPaths = new ArrayDeque<>(normalExit.getPredecessors());
+    while (!uncoveredPaths.isEmpty()) {
+      BasicBlock uncoveredPath = uncoveredPaths.poll();
+      // Stop traversing upwards if we hit the entry block: if the entry block has an non-null,
+      // this case should be handled already by A) because the entry block surely dominates all
+      // normal exits.
+      if (uncoveredPath == code.entryBlock()) {
+        return false;
+      }
+      // Make sure we're not visiting the same block over and over again.
+      if (!visited.add(uncoveredPath)) {
+        // But, if that block is the last one in the queue, the normal exit is not fully covered.
+        if (uncoveredPaths.isEmpty()) {
+          return false;
+        } else {
+          continue;
+        }
+      }
+      boolean pathCovered = false;
+      for (BasicBlock nullCheckedBlock : nullCheckedBlocks) {
+        if (dominatorTree.dominatedBy(uncoveredPath, nullCheckedBlock)) {
+          pathCovered = true;
+          break;
+        }
+      }
+      if (!pathCovered) {
+        // Fan out predecessors one more level.
+        // Note that remaining, unmatched null-checked blocks should cover newly added paths.
+        uncoveredPaths.addAll(uncoveredPath.getPredecessors());
+      }
+    }
+    // Reaching here means that every path to the given normal exit is covered by the set of
+    // null-checked blocks.
+    assert uncoveredPaths.isEmpty();
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 4a34157..06e7332 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -66,7 +66,8 @@
   // invocation with null throwing code if an always-null argument is passed. Also used by Inliner
   // to give a credit to null-safe code, e.g., Kotlin's null safe argument.
   // Note that this bit set takes into account the receiver for instance methods.
-  private BitSet nonNullParamOrThrow = null;
+  private BitSet nonNullParamOrThrow =
+      DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS;
   // Stores information about nullability facts per parameter. If set, that means, the method
   // somehow (e.g., null check, such as arg != null, or NPE-throwing instructions such as array
   // access or another invocation) ensures the corresponding parameter is not null, and that is
@@ -74,7 +75,8 @@
   // normally, the recorded parameter is definitely not null. These facts are used to propagate
   // non-null information through {@link NonNullTracker}.
   // Note that this bit set takes into account the receiver for instance methods.
-  private BitSet nonNullParamOnNormalExits = null;
+  private BitSet nonNullParamOnNormalExits =
+      DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
   private boolean reachabilitySensitive = false;
   private boolean returnValueHasBeenPropagated = false;
 
@@ -435,4 +437,53 @@
     assert this != DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
     return new UpdatableMethodOptimizationInfo(this);
   }
+
+  public void adjustOptimizationInfoAfterRemovingThisParameter() {
+    // cannotBeKept: doesn't depend on `this`
+    // classInitializerMayBePostponed: `this` could trigger <clinit> of the previous holder.
+    classInitializerMayBePostponed = false;
+    // hasBeenInlinedIntoSingleCallSite: then it should not be staticized.
+    hasBeenInlinedIntoSingleCallSite = false;
+    // initializedClassesOnNormalExit: `this` could trigger <clinit> of the previous holder.
+    initializedClassesOnNormalExit =
+        DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
+    // TODO(b/142401154): adjustable
+    returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT;
+    // mayHaveSideEffects: `this` Argument didn't have side effects, so removing it doesn't affect
+    //   whether or not the method may have side effects.
+    // returnValueOnlyDependsOnArguments:
+    //   if the method did before, so it does even after removing `this` Argument
+    // code is not changed, and thus the following *return* info is not changed either.
+    //   * neverReturnsNull
+    //   * neverReturnsNormally
+    //   * returnsConstantNumber
+    //   * returnedConstantNumber
+    //   * returnsConstantString
+    //   * returnedConstantString
+    //   * returnsObjectOfType
+    //   * returnsObjectWithLowerBoundType
+    // inlining: it is not inlined, and thus staticized. No more chance of inlining, though.
+    inlining = InlinePreference.Default;
+    // useIdentifierNameString: code is not changed.
+    // checksNullReceiverBeforeAnySideEffect: no more receiver.
+    checksNullReceiverBeforeAnySideEffect =
+        DefaultMethodOptimizationInfo.UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT;
+    // triggersClassInitBeforeAnySideEffect: code is not changed.
+    triggersClassInitBeforeAnySideEffect =
+        DefaultMethodOptimizationInfo.UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT;
+    // classInlinerEligibility: chances are the method is not an instance method anymore.
+    classInlinerEligibility = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_INLINER_ELIGIBILITY;
+    // trivialInitializerInfo: chances are the enclosing class is
+    trivialInitializerInfo = DefaultMethodOptimizationInfo.UNKNOWN_TRIVIAL_INITIALIZER;
+    // initializerEnablingJavaAssertions: `this` could trigger <clinit> of the previous holder.
+    initializerEnablingJavaAssertions =
+        DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
+    // TODO(b/142401154): adjustable
+    parametersUsages = DefaultMethodOptimizationInfo.UNKNOWN_PARAMETER_USAGE_INFO;
+    nonNullParamOrThrow = DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_OR_THROW_FACTS;
+    nonNullParamOnNormalExits =
+        DefaultMethodOptimizationInfo.NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS;
+    // reachabilitySensitive: doesn't depend on `this`
+    // returnValueHasBeenPropagated: doesn't depend on `this`
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java
new file mode 100644
index 0000000..a29b465
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InlinerUtils.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class InlinerUtils {
+
+  public static void collectAllMonitorEnterValues(
+      IRCode code,
+      Set<DexType> constantMonitorEnterValues,
+      Set<Value> nonConstantMonitorEnterValues) {
+    assert code.metadata().mayHaveMonitorInstruction();
+    for (Monitor monitor : code.<Monitor>instructions(Instruction::isMonitorEnter)) {
+      Value monitorEnterValue = monitor.object().getAliasedValue();
+      addMonitorEnterValue(
+          monitorEnterValue, constantMonitorEnterValues, nonConstantMonitorEnterValues);
+    }
+  }
+
+  public static void addMonitorEnterValue(
+      Value monitorEnterValue,
+      Set<DexType> constantMonitorEnterValues,
+      Set<Value> nonConstantMonitorEnterValues) {
+    assert !monitorEnterValue.hasAliasedValue();
+    if (monitorEnterValue.isPhi() || !monitorEnterValue.definition.isConstClass()) {
+      nonConstantMonitorEnterValues.add(monitorEnterValue);
+    } else {
+      constantMonitorEnterValues.add(monitorEnterValue.definition.asConstClass().getValue());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index b5c96d4..467a5b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -104,9 +104,6 @@
   public void reportRecursiveMethod() {}
 
   @Override
-  public void reportSynchronizedMethod() {}
-
-  @Override
   public void reportUnknownTarget() {}
 
   @Override
@@ -122,6 +119,10 @@
   public void reportWillExceedInstructionBudget(int numberOfInstructions, int threshold) {}
 
   @Override
+  public void reportWillExceedMonitorEnterValuesBudget(
+      int numberOfMonitorEnterValuesAfterInlining, int threshold) {}
+
+  @Override
   public boolean verifyReasonHasBeenReported() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 1e869eb..ab80544 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -101,8 +101,6 @@
 
   public abstract void reportRecursiveMethod();
 
-  public abstract void reportSynchronizedMethod();
-
   abstract void reportUnknownTarget();
 
   public abstract void reportUnsafeConstructorInliningDueToFinalFieldAssignment(
@@ -115,5 +113,8 @@
 
   public abstract void reportWillExceedInstructionBudget(int numberOfInstructions, int threshold);
 
+  public abstract void reportWillExceedMonitorEnterValuesBudget(
+      int numberOfMonitorEnterValuesAfterInlining, int threshold);
+
   public abstract boolean verifyReasonHasBeenReported();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index dcc122b..7d25661 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -210,11 +210,6 @@
   }
 
   @Override
-  public void reportSynchronizedMethod() {
-    print("synchronized methods are not inlined.");
-  }
-
-  @Override
   public void reportUnknownTarget() {
     print("could not find a single target.");
   }
@@ -252,6 +247,16 @@
   }
 
   @Override
+  public void reportWillExceedMonitorEnterValuesBudget(
+      int numberOfMonitorEnterValuesAfterInlining, int threshold) {
+    printWithExceededThreshold(
+        "could negatively impact register allocation due to the number of monitor instructions",
+        "estimated number of locks after inlining",
+        numberOfMonitorEnterValuesAfterInlining,
+        threshold);
+  }
+
+  @Override
   public boolean verifyReasonHasBeenReported() {
     assert reasonHasBeenReported;
     return true;
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 ad82955..89aeb2d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -225,6 +225,8 @@
   public int inliningInstructionLimit = 3;
   // This defines how many instructions of inlinees we can inlinee overall.
   public int inliningInstructionAllowance = 1500;
+  // Maximum number of distinct values in a method that may be used in a monitor-enter instruction.
+  public int inliningMonitorEnterValuesAllowance = 4;
   // Maximum number of control flow resolution blocks that setup the register state before
   // the actual catch handler allowed when inlining. Threshold found empirically by testing on
   // GMS Core.
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 b6cbe41..8e3fe1a 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -10,9 +10,44 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import java.util.Iterator;
 import java.util.ListIterator;
+import java.util.NoSuchElementException;
 import java.util.function.Predicate;
 
 public class IteratorUtils {
+
+  public static <T, S extends T> Iterator<S> filter(Iterator<T> iterator, Predicate<T> predicate) {
+    return new Iterator<S>() {
+
+      private S next = advance();
+
+      @SuppressWarnings("unchecked")
+      private S advance() {
+        while (iterator.hasNext()) {
+          T element = iterator.next();
+          if (predicate.test(element)) {
+            return (S) element;
+          }
+        }
+        return null;
+      }
+
+      @Override
+      public boolean hasNext() {
+        return next != null;
+      }
+
+      @Override
+      public S next() {
+        S current = next;
+        if (current == null) {
+          throw new NoSuchElementException();
+        }
+        next = advance();
+        return current;
+      }
+    };
+  }
+
   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/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 370d5e2..79f4f6c 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -12,6 +12,15 @@
 
 public class ListUtils {
 
+  public static <T> T first(List<T> list) {
+    return list.get(0);
+  }
+
+  public static <T> T last(List<T> list) {
+    assert list instanceof ArrayList;
+    return list.get(list.size() - 1);
+  }
+
   public static <T> int lastIndexMatching(List<T> list, Predicate<T> tester) {
     for (int i = list.size() - 1; i >= 0; i--) {
       if (tester.test(list.get(i))) {
diff --git a/src/test/examples/getmembers/B.java b/src/test/examples/getmembers/B.java
index d29d0a2..1f41948 100644
--- a/src/test/examples/getmembers/B.java
+++ b/src/test/examples/getmembers/B.java
@@ -15,9 +15,9 @@
     return bazResult;
   }
 
-  synchronized static String inliner() throws Exception {
+  @NeverInline
+  static String inliner() throws Exception {
     B self = new B();
     return self.toBeInlined();
   }
-
 }
diff --git a/src/test/examples/getmembers/NeverInline.java b/src/test/examples/getmembers/NeverInline.java
new file mode 100644
index 0000000..0c5a4ef
--- /dev/null
+++ b/src/test/examples/getmembers/NeverInline.java
@@ -0,0 +1,10 @@
+// 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 getmembers;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverInline {}
diff --git a/src/test/examples/getmembers/keep-rules.txt b/src/test/examples/getmembers/keep-rules.txt
index 9319c68..9fbea4d 100644
--- a/src/test/examples/getmembers/keep-rules.txt
+++ b/src/test/examples/getmembers/keep-rules.txt
@@ -19,3 +19,7 @@
   java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]);
   java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);
 }
+
+-neverinline class * {
+  @getmembers.NeverInline <methods>;
+}
diff --git a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index ed42134..e9e7a58 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -327,7 +327,10 @@
 
       @SomeAnnotation(4)
       static void annotatedStaticMethod() {
-        synchronized (AnnotatedInterface.class) { } // Do not inline
+        // Bogus body to be included into the root set: if interface desugaring is on, root set
+        // builder will skip marking methods without code.
+        synchronized (AnnotatedInterface.class) {
+        }
       }
     }
 
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index bdc5b16..6695f46 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -209,7 +209,10 @@
 
   @Override
   public ProguardTestBuilder setMinApi(AndroidApiLevel minApiLevel) {
-    throw new Unimplemented("No support for setting min api");
+    if (backend == Backend.DEX) {
+      throw new Unimplemented("No support for setting min api");
+    }
+    return self();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 75fe21f..0d8e03c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -44,10 +44,8 @@
           "-keepclasseswithmembers public class * {",
           "    public static void main(java.lang.String[]);",
           "}",
-          "-keepclasseswithmembers interface lambdadesugaringnplus."
-              + "LambdasWithStaticAndDefaultMethods$B38302860$AnnotatedInterface{",
-          "    *;",
-          "}",
+          "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
+          "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
           "-keepattributes *Annotation*",
           "-dontobfuscate",
           "-allowaccessmodification");
@@ -197,6 +195,7 @@
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
+        .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
@@ -206,6 +205,7 @@
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
+        .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
         // TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
@@ -221,6 +221,7 @@
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
+        .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
         .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
@@ -230,6 +231,7 @@
         .withMinApiLevel(AndroidApiLevel.N)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
+        .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
         // TODO(b/120814598): Should be 5. Some lambdas are not class inlined because parameter
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 019fed1..0305053 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -169,6 +169,8 @@
     return self();
   }
 
+  /** @deprecated use {@link #setMinApi(AndroidApiLevel)} instead. */
+  @Deprecated
   public T setMinApi(TestRuntime runtime) {
     if (runtime.isDex()) {
       setMinApi(runtime.asDex().getMinApiLevel());
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/ConversionIntroduceInterfaceMethodTest.java
new file mode 100644
index 0000000..f38aa3b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/ConversionIntroduceInterfaceMethodTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.corelib.conversionTests;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.function.Consumer;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+public class ConversionIntroduceInterfaceMethodTest extends APIConversionTestBase {
+
+  @Test
+  public void testNoInterfaceMethods() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(
+            MyCollectionInterface.class,
+            MyCollectionInterfaceAbstract.class,
+            MyCollection.class,
+            Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "action called from j$ consumer",
+                "forEach called",
+                "action called from java consumer",
+                "forEach called"));
+  }
+
+  static class CustomLibClass {
+
+    @SuppressWarnings({"unchecked", "WeakerAccess"})
+    public static void callForeach(Iterable iterable) {
+      iterable.forEach(x -> System.out.println("action called from java consumer"));
+    }
+  }
+
+  static class Executor {
+
+    @SuppressWarnings("RedundantOperationOnEmptyContainer")
+    public static void main(String[] args) {
+      MyCollection<String> strings = new MyCollection<>();
+      // Call foreach with j$ consumer.
+      strings.forEach(x -> System.out.println("action called from j$ consumer"));
+      // Call foreach with java consumer.
+      CustomLibClass.callForeach(strings);
+    }
+  }
+
+  interface MyCollectionInterface<E> extends Collection<E> {
+
+    // The following method override a method from Iterable and use a desugared type.
+    // API conversion is required.
+    @Override
+    default void forEach(Consumer<? super E> action) {
+      action.accept(null);
+      System.out.println("forEach called");
+    }
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  static class MyCollection<E> implements MyCollectionInterface<E> {
+
+    @Override
+    public int size() {
+      return 0;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return false;
+    }
+
+    @NotNull
+    @Override
+    public Iterator<E> iterator() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public Object[] toArray() {
+      return new Object[0];
+    }
+
+    @NotNull
+    @Override
+    public <T> T[] toArray(@NotNull T[] a) {
+      return null;
+    }
+
+    @Override
+    public boolean add(E e) {
+      return false;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return false;
+    }
+
+    @Override
+    public boolean containsAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean addAll(@NotNull Collection<? extends E> c) {
+      return false;
+    }
+
+    @Override
+    public boolean removeAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public boolean retainAll(@NotNull Collection<?> c) {
+      return false;
+    }
+
+    @Override
+    public void clear() {}
+  }
+
+  interface MyCollectionInterfaceAbstract<E> extends Collection<E> {
+
+    // The following method override a method from Iterable and use a desugared type.
+    // API conversion is required.
+    @Override
+    void forEach(Consumer<? super E> action);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index e60900c..fbcc6a1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -378,6 +378,7 @@
             .addKeepAttributes("LineNumberTable")
             .addOptionsModification(this::configure)
             .allowAccessModification()
+            .enableInliningAnnotations()
             .noMinification()
             .run(main)
             .assertSuccessWithOutput(javaOutput);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
index 130f895..566d3f9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.lambdas;
 
+import com.android.tools.r8.NeverInline;
+
 public class LambdasTestClass {
   private static int ID = 0;
 
@@ -39,14 +41,16 @@
     return next();
   }
 
-  private synchronized void testStatelessLambda() {
+  @NeverInline
+  private void testStatelessLambda() {
     IfaceUtil.act(() -> next());
     IfaceUtil.act(LambdasTestClass::next);
     IfaceUtil.act(LambdasTestClass::exact);
     IfaceUtil.act(LambdasTestClass::almost);
   }
 
-  private synchronized void testStatefulLambda(String a, String b) {
+  @NeverInline
+  private void testStatefulLambda(String a, String b) {
     IfaceUtil.act(() -> a);
     IfaceUtil.act(() -> a + b);
     IfaceUtil.act((a + b)::toLowerCase);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
index dcfe291..b3a7ce8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -5,20 +5,17 @@
 package com.android.tools.r8.ir.optimize.inliner;
 
 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.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.util.Iterator;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -26,71 +23,89 @@
 @RunWith(Parameterized.class)
 public class InlineSynchronizedTest extends TestBase {
 
-  private static final List<String> METHOD_NAMES =
-      ImmutableList.of(
-          "println",
-          "normalInlinedSynchronized",
-          "classInlinedSynchronized",
-          "normalInlinedControl",
-          "classInlinedControl");
-
-  @Parameterized.Parameters(name = "{1}")
-  public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  private final Backend backend;
   private final TestParameters parameters;
 
-  public InlineSynchronizedTest(TestParameters parameters, Backend backend) {
+  public InlineSynchronizedTest(TestParameters parameters) {
     this.parameters = parameters;
-    this.backend = backend;
   }
 
   @Test
   public void test() throws Exception {
-    CodeInspector codeInspector =
-        testForR8(backend)
-            .addProgramClasses(InlineSynchronizedTestClass.class)
-            .addKeepMainRule(InlineSynchronizedTestClass.class)
-            .enableInliningAnnotations()
-            .noMinification()
-            .compile()
-            .inspector();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineSynchronizedTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+              assertThat(testClassSubject, isPresent());
 
-    ClassSubject classSubject = codeInspector.clazz(InlineSynchronizedTestClass.class);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.mainMethod();
-    Iterator<InstructionSubject> it = methodSubject.iterateInstructions();
-    int[] counts = new int[METHOD_NAMES.size()];
-    while (it.hasNext()) {
-      InstructionSubject instruction = it.next();
-      if (!instruction.isInvoke()) {
-        continue;
-      }
-      DexString invokedName = ((InvokeInstructionSubject) instruction).invokedMethod().name;
-      int idx = METHOD_NAMES.indexOf(invokedName.toString());
-      if (idx >= 0) {
-        ++counts[idx];
-      }
-    }
-    // Synchronized methods can never be inlined.
-    assertCount(counts, "normalInlinedSynchronized", 1);
-    assertCount(counts, "classInlinedSynchronized", 1);
-    // Only the never-merge method is inlined, classInlining should run on the kept class.
-    assertCount(counts, "normalInlinedControl", 0);
-    assertCount(counts, "classInlinedControl", 1);
-    // Double check the total.
-    int total = 0;
-    for (int i = 0; i < counts.length; ++i) {
-      total += counts[i];
-    }
-    assertEquals(4, total);
+              // Check that there is a single monitor-enter instruction (for the normal inlining
+              // case).
+              assertEquals(
+                  1,
+                  testClassSubject
+                      .mainMethod()
+                      .streamInstructions()
+                      .filter(InstructionSubject::isMonitorEnter)
+                      .count());
+
+              // Check that there are two monitor-exit instructions (for the normal inlining case,
+              // one for the normal exit and one for the exceptional exit).
+              assertEquals(
+                  2,
+                  testClassSubject
+                      .mainMethod()
+                      .streamInstructions()
+                      .filter(InstructionSubject::isMonitorExit)
+                      .count());
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertThat(
+                  aClassSubject.uniqueMethodWithName("normalInlinedSynchronized"),
+                  not(isPresent()));
+
+              ClassSubject bClassSubject = inspector.clazz(B.class);
+              assertThat(bClassSubject, not(isPresent()));
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            "A::normalInlinedSynchronized", "B::classInlinedSynchronized");
   }
 
-  private static void assertCount(int counts[], String methodName, int expectedCount) {
-    int idx = METHOD_NAMES.indexOf(methodName);
-    assert idx >= 0;
-    assertEquals(expectedCount, counts[idx]);
+  static class TestClass {
+
+    public static void main(String[] args) {
+      // Test normal inlining.
+      new A().normalInlinedSynchronized();
+
+      // Test class-inlining.
+      new B().classInlinedSynchronized();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    synchronized void normalInlinedSynchronized() {
+      System.out.println("A::normalInlinedSynchronized");
+    }
+  }
+
+  static class B {
+
+    @NeverInline
+    synchronized void classInlinedSynchronized() {
+      System.out.println("B::classInlinedSynchronized");
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
deleted file mode 100644
index 45d3a5c..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.inliner;
-
-import com.android.tools.r8.NeverInline;
-
-class InlineSynchronizedTestClass {
-  private synchronized void normalInlinedSynchronized() {
-    System.out.println("InlineSynchronizedTestClass::normalInlinedSynchronized");
-  }
-
-  public synchronized void classInlinedSynchronized() {
-    System.out.println("InlineSynchronizedTestClass::classInlinedSynchronized");
-  }
-
-  private void normalInlinedControl() {
-    System.out.println("InlineSynchronizedTestClass::normalInlinedControl");
-  }
-
-  @NeverInline
-  public void classInlinedControl() {
-    System.out.println("InlineSynchronizedTestClass::classInlinedControl");
-  }
-
-  public static void main(String[] args) {
-    // Test normal inlining.
-    InlineSynchronizedTestClass testClass = new InlineSynchronizedTestClass();
-    testClass.normalInlinedSynchronized();
-    testClass.normalInlinedControl();
-
-    // Test class-inlining.
-    new InlineSynchronizedTestClass().classInlinedSynchronized();
-    new InlineSynchronizedTestClass().classInlinedControl();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
new file mode 100644
index 0000000..a8578e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
@@ -0,0 +1,133 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.sync;
+
+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;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.inliner.sync.InlineStaticSynchronizedMethodTest.TestClass.RunnableImpl;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.Thread.State;
+import java.util.ArrayList;
+import java.util.Collections;
+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 InlineStaticSynchronizedMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public InlineStaticSynchronizedMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineStaticSynchronizedMethodTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::verifySynchronizedMethodsAreInlined)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            "t1 START", "m1 ENTER", "t2 START", "m1 EXIT", "m2 ENTER", "m2 EXIT");
+  }
+
+  private void verifySynchronizedMethodsAreInlined(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(RunnableImpl.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.uniqueMethodWithName("m1"), not(isPresent()));
+    assertThat(classSubject.uniqueMethodWithName("m2"), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    private static List<String> logs = Collections.synchronizedList(new ArrayList<>());
+
+    private static volatile Thread t1 = new Thread(new RunnableImpl(1));
+    private static volatile Thread t2 = new Thread(new RunnableImpl(2));
+
+    public static void main(String[] args) {
+      System.out.println("t1 START");
+      t1.start();
+      waitUntil(() -> logs.contains("m1 ENTER"));
+      System.out.println("t2 START");
+      t2.start();
+    }
+
+    static void log(String message) {
+      logs.add(message);
+      System.out.println(message);
+    }
+
+    static void waitUntil(BooleanSupplier condition) {
+      while (!condition.getAsBoolean()) {
+        try {
+          Thread.sleep(50);
+        } catch (InterruptedException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+
+    static class RunnableImpl implements Runnable {
+
+      int which;
+
+      RunnableImpl(int which) {
+        this.which = which;
+      }
+
+      @Override
+      public void run() {
+        if (which == 1) {
+          m1();
+        } else {
+          m2();
+        }
+      }
+
+      static synchronized void m1() {
+        log("m1 ENTER");
+        // Intentionally not using a lambda, since we do not allow inlining of methods with an
+        // invoke-custom instruction. (This only makes a difference for the CF backend.)
+        waitUntil(
+            new BooleanSupplier() {
+
+              @Override
+              public boolean getAsBoolean() {
+                return t2.getState() == State.BLOCKED;
+              }
+            });
+        log("m1 EXIT");
+      }
+
+      static synchronized void m2() {
+        log("m2 ENTER");
+        log("m2 EXIT");
+      }
+    }
+  }
+
+  interface BooleanSupplier {
+
+    boolean getAsBoolean();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
new file mode 100644
index 0000000..18d1deb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner.sync;
+
+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;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlinerMonitorEnterValuesThresholdTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final int threshold;
+
+  @Parameters(name = "{0},  threshold: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), ImmutableList.of(2, 3));
+  }
+
+  public InlinerMonitorEnterValuesThresholdTest(TestParameters parameters, int threshold) {
+    this.parameters = parameters;
+    this.threshold = threshold;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(options -> options.inliningMonitorEnterValuesAllowance = threshold)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject.mainMethod(), isPresent());
+    assertThat(classSubject.uniqueMethodWithName("m1"), not(isPresent()));
+    assertThat(classSubject.uniqueMethodWithName("m2"), not(isPresent()));
+    if (threshold == 2) {
+      assertThat(classSubject.uniqueMethodWithName("m3"), isPresent());
+    } else {
+      assert threshold == 3;
+      assertThat(classSubject.uniqueMethodWithName("m3"), not(isPresent()));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      synchronized (TestClass.class) {
+        // Method m1() contributes with 1 extra monitor-enter value and should therefore always be
+        // inlined since the threshold is 2 or 3.
+        m1();
+
+        // Method m2() contributes with no extra monitor-enter values since it uses TestClass.class
+        // as a lock, which is already used as a lock in the enclosing class. Therefore, m2() should
+        // always be inlined.
+        m2();
+
+        // Method m3() contributes with 1 extra monitor-enter value and should therefore only be
+        // inlined if the threshold is 3.
+        m3();
+      }
+    }
+
+    private static void m1() {
+      synchronized (new Object()) {
+        System.out.print("Hello");
+      }
+    }
+
+    private static synchronized void m2() {
+      System.out.print(" world");
+    }
+
+    private static void m3() {
+      synchronized (new Object()) {
+        System.out.println("!");
+      }
+    }
+  }
+}
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 8f4aca9..2cd766b 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -65,8 +65,11 @@
         testForR8(parameters.getBackend())
             .addProgramFiles(Paths.get(appFileName))
             .addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
+            .allowUnusedProguardConfigurationRules()
+            .enableProguardTestOptions()
             .setMinApi(parameters.getRuntime())
-            .compile().inspector();
+            .compile()
+            .inspector();
     inspection.accept(parameters, codeInspector);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index 8585a9c..9e92530 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -31,10 +31,10 @@
   private final TestParameters parameters;
   private final boolean defaultEnumValueInAnnotation;
 
-  @Parameterized.Parameters(name = "Backend: {0}, default value in annotation: {1}")
+  @Parameterized.Parameters(name = "{0}, default value in annotation: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   public B137392797(TestParameters parameters, boolean defaultEnumValueInAnnotation) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index ab0fc8b..d8afbb3 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -50,6 +50,10 @@
           || this == R8_CF;
     }
 
+    public boolean isProguard() {
+      return this == PROGUARD5 || this == PROGUARD6 || this == PROGUARD6_THEN_D8;
+    }
+
     public boolean isFullModeR8() {
       return this == R8
           || this == R8_CF;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 2b6c5db..268a673 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -9,11 +9,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 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.Collection;
@@ -25,180 +29,231 @@
 
 @RunWith(Parameterized.class)
 public class IfOnAccessModifierTest extends ProguardCompatibilityTestBase {
+
   private static final List<Class<?>> CLASSES =
-      ImmutableList.of(ClassForIf.class, ClassForSubsequent.class, MainForAccessModifierTest.class);
+      ImmutableList.of(
+          ClassForIf.class,
+          ClassForSubsequent.class,
+          MainForAccessModifierTest.class,
+          NeverClassInline.class,
+          NeverInline.class);
 
+  private final TestParameters parameters;
   private final Shrinker shrinker;
-  private final MethodSignature nonPublicMethod;
-  private final MethodSignature publicMethod;
 
-  public IfOnAccessModifierTest(Shrinker shrinker) {
+  public IfOnAccessModifierTest(TestParameters parameters, Shrinker shrinker) {
+    this.parameters = parameters;
     this.shrinker = shrinker;
-    nonPublicMethod = new MethodSignature("nonPublicMethod", "void", ImmutableList.of());
-    publicMethod = new MethodSignature("publicMethod", "void", ImmutableList.of());
   }
 
-  @Parameters(name = "shrinker: {0}")
-  public static Collection<Object> data() {
-    return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
+  @Parameters(name = "{0}, shrinker: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8));
+  }
+
+  private TestShrinkerBuilder<?, ?, ?, ?, ?> getTestBuilder() {
+    switch (shrinker) {
+      case PROGUARD6:
+        assertTrue(parameters.isCfRuntime());
+        return testForProguard();
+      case R8:
+        return testForR8(parameters.getBackend())
+            .allowUnusedProguardConfigurationRules()
+            .enableClassInliningAnnotations()
+            .enableInliningAnnotations();
+      default:
+        throw new Unreachable();
+    }
   }
 
   @Test
   public void ifOnPublic_noPublicClassForIfRule() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-repackageclasses 'top'",
-        "-keep class **.Main* {",
-        "  public static void callIfNonPublic();",
-        "}",
-        "-if public class **.ClassForIf {",
-        "  <methods>;",
-        "}",
-        "-keep,allowobfuscation class **.ClassForSubsequent {",
-        "  public <methods>;",
-        "}"
-    );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
-    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, not(isPresent()));
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, isPresent());
-    assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+    assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
 
-    classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    assertThat(classSubject, not(isPresent()));
+    getTestBuilder()
+        .addProgramClasses(CLASSES)
+        .addKeepRules(
+            "-repackageclasses 'top'",
+            "-keep class **.Main* {",
+            "  public static void callIfNonPublic();",
+            "}",
+            "-if public class **.ClassForIf {",
+            "  <methods>;",
+            "}",
+            "-keep,allowobfuscation class **.ClassForSubsequent {",
+            "  public <methods>;",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+              assertThat(classSubject, isPresent());
+              MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, not(isPresent()));
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, isPresent());
+              assertFalse(methodSubject.getMethod().accessFlags.isPublic());
+              classSubject = inspector.clazz(ClassForSubsequent.class);
+              assertThat(classSubject, not(isPresent()));
+            });
   }
 
   @Test
   public void ifOnNonPublic_keepOnPublic() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-printmapping",
-        "-repackageclasses 'top'",
-        "-allowaccessmodification",
-        "-keep class **.Main* {",
-        "  public static void callIfNonPublic();",
-        "}",
-        "-if class **.ClassForIf {",
-        "  !public <methods>;",
-        "}",
-        "-keep,allowobfuscation class **.ClassForSubsequent {",
-        "  public <methods>;",
-        "}"
-    );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
-    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, not(isPresent()));
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, isPresent());
-    assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+    assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
 
-    classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    assertThat(classSubject, isPresent());
-    methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, isPresent());
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, not(isPresent()));
+    getTestBuilder()
+        .addProgramClasses(CLASSES)
+        .addKeepRules(
+            "-repackageclasses 'top'",
+            "-allowaccessmodification",
+            "-keep class **.Main* {",
+            "  public static void callIfNonPublic();",
+            "}",
+            "-if class **.ClassForIf {",
+            "  !public <methods>;",
+            "}",
+            "-keep,allowobfuscation class **.ClassForSubsequent {",
+            "  public <methods>;",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+              assertThat(classSubject, isPresent());
+              MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, not(isPresent()));
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, isPresent());
+              assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+              classSubject = inspector.clazz(ClassForSubsequent.class);
+              assertThat(classSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, not(isPresent()));
+            });
   }
 
   @Test
   public void ifOnNonPublic_keepOnNonPublic() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-printmapping",
-        "-repackageclasses 'top'",
-        "-allowaccessmodification",
-        "-keep class **.Main* {",
-        "  public static void callIfNonPublic();",
-        "}",
-        "-if class **.ClassForIf {",
-        "  !public <methods>;",
-        "}",
-        "-keep,allowobfuscation class **.ClassForSubsequent {",
-        "  !public <methods>;",
-        "}"
-    );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
-    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, not(isPresent()));
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, isPresent());
-    assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+    assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
 
-    classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    assertThat(classSubject, isPresent());
-    methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, not(isPresent()));
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, isPresent());
-    assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+    getTestBuilder()
+        .addProgramClasses(CLASSES)
+        .addKeepRules(
+            "-repackageclasses 'top'",
+            "-allowaccessmodification",
+            "-keep class **.Main* {",
+            "  public static void callIfNonPublic();",
+            "}",
+            "-if class **.ClassForIf {",
+            "  !public <methods>;",
+            "}",
+            "-keep,allowobfuscation class **.ClassForSubsequent {",
+            "  !public <methods>;",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+              assertThat(classSubject, isPresent());
+              MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, not(isPresent()));
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, isPresent());
+              assertTrue(methodSubject.getMethod().accessFlags.isPublic());
+
+              classSubject = inspector.clazz(ClassForSubsequent.class);
+              assertThat(classSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, not(isPresent()));
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, isPresent());
+              assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+            });
   }
 
   @Test
   public void ifOnPublic_keepOnPublic() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-printmapping",
-        "-repackageclasses 'top'",
-        "-allowaccessmodification",
-        "-keep class **.Main* {",
-        "  public static void callIfPublic();",
-        "}",
-        "-if class **.ClassForIf {",
-        "  public <methods>;",
-        "}",
-        "-keep,allowobfuscation class **.ClassForSubsequent {",
-        "  public <methods>;",
-        "}"
-    );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
-    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, isPresent());
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, not(isPresent()));
+    assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
 
-    classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    assertThat(classSubject, isPresent());
-    methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, isPresent());
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, not(isPresent()));
+    getTestBuilder()
+        .addProgramClasses(CLASSES)
+        .addKeepRules(
+            "-repackageclasses 'top'",
+            "-allowaccessmodification",
+            "-keep class **.Main* {",
+            "  public static void callIfPublic();",
+            "}",
+            "-if class **.ClassForIf {",
+            "  public <methods>;",
+            "}",
+            "-keep,allowobfuscation class **.ClassForSubsequent {",
+            "  public <methods>;",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+              assertThat(classSubject, isPresent());
+              MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, not(isPresent()));
+
+              classSubject = inspector.clazz(ClassForSubsequent.class);
+              assertThat(classSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, not(isPresent()));
+            });
   }
 
   @Test
   public void ifOnPublic_keepOnNonPublic() throws Exception {
-    List<String> config = ImmutableList.of(
-        "-printmapping",
-        "-repackageclasses 'top'",
-        "-allowaccessmodification",
-        "-keep class **.Main* {",
-        "  public static void callIfPublic();",
-        "}",
-        "-if class **.ClassForIf {",
-        "  public <methods>;",
-        "}",
-        "-keep,allowobfuscation class **.ClassForSubsequent {",
-        "  !public <methods>;",
-        "}"
-    );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
-    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, isPresent());
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, not(isPresent()));
+    assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
 
-    classSubject = codeInspector.clazz(ClassForSubsequent.class);
-    assertThat(classSubject, isPresent());
-    methodSubject = classSubject.method(publicMethod);
-    assertThat(methodSubject, not(isPresent()));
-    methodSubject = classSubject.method(nonPublicMethod);
-    assertThat(methodSubject, isPresent());
-    assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+    getTestBuilder()
+        .addProgramClasses(CLASSES)
+        .addKeepRules(
+            "-repackageclasses 'top'",
+            "-allowaccessmodification",
+            "-keep class **.Main* {",
+            "  public static void callIfPublic();",
+            "}",
+            "-if class **.ClassForIf {",
+            "  public <methods>;",
+            "}",
+            "-keep,allowobfuscation class **.ClassForSubsequent {",
+            "  !public <methods>;",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject classSubject = inspector.clazz(ClassForIf.class);
+              assertThat(classSubject, isPresent());
+              MethodSubject methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, not(isPresent()));
+
+              classSubject = inspector.clazz(ClassForSubsequent.class);
+              assertThat(classSubject, isPresent());
+              methodSubject = classSubject.uniqueMethodWithName("publicMethod");
+              assertThat(methodSubject, not(isPresent()));
+              methodSubject = classSubject.uniqueMethodWithName("nonPublicMethod");
+              assertThat(methodSubject, isPresent());
+              assertEquals(shrinker.isR8(), methodSubject.getMethod().accessFlags.isPublic());
+            });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
index 1b5bd75..49d75f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTestClasses.java
@@ -3,14 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.ifrule;
 
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
 class ClassForIf {
   ClassForIf() {
   }
 
+  @NeverInline
   synchronized void nonPublicMethod() {
     System.out.println("ClassForIf::nonPublicMethod");
   }
 
+  @NeverInline
   synchronized public void publicMethod() {
     System.out.println("ClassForIf::publicMethod");
   }
@@ -20,10 +26,12 @@
   ClassForSubsequent() {
   }
 
+  @NeverInline
   synchronized void nonPublicMethod() {
     System.out.println("ClassForSubsequent::nonPublicMethod");
   }
 
+  @NeverInline
   synchronized public void publicMethod() {
     System.out.println("ClassForSubsequent::publicMethod");
   }