Refactor utils that collect method optimization info.

Bug: 141656615
Change-Id: If0ec84a917b22414918e0e24cdec923bab17da90
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/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index b7d3dcd..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 {
@@ -1155,620 +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,
-    //  (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 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 =
@@ -2203,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/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/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;
+  }
+}