Defer redundant block removal to end of class inliner

Change-Id: If1e6fd3f1a8041f629312ec800548df31e465b15
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 8b5d9ee..cf010f5 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
@@ -729,7 +729,7 @@
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it does not get collected by merger.
       classInliner.processMethodCode(
-          context, code, feedback, methodProcessor, methodProcessingContext);
+          context, code, feedback, methodProcessor, methodProcessingContext, timing);
       timing.end();
       code.removeRedundantBlocks();
       assert code.isConsistentSSA(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 76250ff..57efcdf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -134,7 +134,8 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
+      MethodProcessingContext methodProcessingContext,
+      Timing timing) {
     LazyBox<InliningOracle> defaultOracle =
         new LazyBox<>(
             () ->
@@ -146,14 +147,17 @@
                     code));
 
     // Collect all the new-instance and static-get instructions in the code before inlining.
+    timing.begin("Compute roots");
     List<Instruction> roots =
         Lists.newArrayList(code.instructions(insn -> insn.isNewInstance() || insn.isStaticGet()));
+    timing.end();
 
     // We loop inlining iterations until there was no inlining, but still use same set
     // of roots to avoid infinite inlining. Looping makes possible for some roots to
     // become eligible after other roots are inlined.
     boolean anyInlinedGeneratedMessageLiteBuilders = false;
     boolean anyInlinedMethods = false;
+    boolean changed = false;
     boolean repeat;
     do {
       repeat = false;
@@ -165,40 +169,46 @@
             new InlineCandidateProcessor(appView, code, inliner, methodProcessor, method, root);
 
         // Assess eligibility of instance and class.
-        EligibilityStatus status = processor.isInstanceEligible();
-        if (status != EligibilityStatus.ELIGIBLE) {
-          // This root will never be inlined.
-          rootsIterator.remove();
-          continue;
-        }
-        status = isClassEligible(processor.getEligibleClass());
-        if (status != EligibilityStatus.ELIGIBLE) {
-          // This root will never be inlined.
-          rootsIterator.remove();
-          continue;
+        try (Timing t0 = timing.begin("Compute instance eligibility")) {
+          EligibilityStatus status = processor.isInstanceEligible();
+          if (status != EligibilityStatus.ELIGIBLE) {
+            // This root will never be inlined.
+            rootsIterator.remove();
+            continue;
+          }
+          status = isClassEligible(processor.getEligibleClass());
+          if (status != EligibilityStatus.ELIGIBLE) {
+            // This root will never be inlined.
+            rootsIterator.remove();
+            continue;
+          }
         }
 
         // Assess users eligibility and compute inlining of direct calls and extra methods needed.
-        InstructionOrPhi ineligibleUser = processor.areInstanceUsersEligible(defaultOracle);
-        if (ineligibleUser != null) {
-          // This root may succeed if users change in future.
-          continue;
+        try (Timing t0 = timing.begin("Compute users eligibility")) {
+          InstructionOrPhi ineligibleUser = processor.areInstanceUsersEligible(defaultOracle);
+          if (ineligibleUser != null) {
+            // This root may succeed if users change in future.
+            continue;
+          }
         }
 
         // Is inlining allowed.
         InliningIRProvider inliningIRProvider =
             new InliningIRProvider(
                 appView, method, code, inliner.getLensCodeRewriter(), methodProcessor);
-        ClassInlinerCostAnalysis costAnalysis =
-            new ClassInlinerCostAnalysis(appView, inliningIRProvider, processor.getReceivers());
-        if (costAnalysis.willExceedInstructionBudget(
-            code,
-            processor.getEligibleClass(),
-            processor.getDirectInlinees(),
-            processor.getIndirectInlinees())) {
-          // This root is unlikely to be inlined in the future.
-          rootsIterator.remove();
-          continue;
+        try (Timing t0 = timing.begin("Analyze cost")) {
+          ClassInlinerCostAnalysis costAnalysis =
+              new ClassInlinerCostAnalysis(appView, inliningIRProvider, processor.getReceivers());
+          if (costAnalysis.willExceedInstructionBudget(
+              code,
+              processor.getEligibleClass(),
+              processor.getDirectInlinees(),
+              processor.getIndirectInlinees())) {
+            // This root is unlikely to be inlined in the future.
+            rootsIterator.remove();
+            continue;
+          }
         }
 
         if (appView.protoShrinker() != null && root.isNewInstance()) {
@@ -212,28 +222,33 @@
 
         // Inline the class instance.
         AffectedValues affectedValues = new AffectedValues();
-        try {
-          anyInlinedMethods |=
-              processor.processInlining(code, affectedValues, feedback, inliningIRProvider);
-        } catch (IllegalClassInlinerStateException e) {
-          // We introduced a user that we cannot handle in the class inliner as a result of force
-          // inlining. Abort gracefully from class inlining without removing the instance.
-          //
-          // Alternatively we would need to collect additional information about the behavior of
-          // methods (which is bad for memory), or we would need to analyze the called methods
-          // before inlining them. The latter could be good solution, since we are going to build IR
-          // for the methods that need to be inlined anyway.
-          assert false;
-          anyInlinedMethods = true;
+        try (Timing t0 = timing.begin("Perform inlining")) {
+          try {
+            anyInlinedMethods |=
+                processor.processInlining(
+                    code, affectedValues, feedback, inliningIRProvider, timing);
+          } catch (IllegalClassInlinerStateException e) {
+            // We introduced a user that we cannot handle in the class inliner as a result of force
+            // inlining. Abort gracefully from class inlining without removing the instance.
+            //
+            // Alternatively we would need to collect additional information about the behavior of
+            // methods (which is bad for memory), or we would need to analyze the called methods
+            // before inlining them. The latter could be good solution, since we are going to build
+            // IR for the methods that need to be inlined anyway.
+            assert false;
+            anyInlinedMethods = true;
+          }
         }
+        changed = true;
 
         // Restore normality.
-        code.removeAllDeadAndTrivialPhis(affectedValues);
-        affectedValues.narrowingWithAssumeRemoval(appView, code);
-        code.removeRedundantBlocks();
-        assert code.isConsistentSSA(appView);
-        rootsIterator.remove();
-        repeat = true;
+        try (Timing t0 = timing.begin("Restore consistent SSA")) {
+          code.removeAllDeadAndTrivialPhis(affectedValues);
+          affectedValues.narrowingWithAssumeRemoval(appView, code);
+          assert code.isConsistentSSAAllowingRedundantBlocks(appView);
+          rootsIterator.remove();
+          repeat = true;
+        }
       }
     } while (repeat);
 
@@ -245,6 +260,11 @@
                   method, code, feedback, methodProcessor, methodProcessingContext, inliner));
     }
 
+    if (changed) {
+      // Ensure no redundant blocks.
+      code.removeRedundantBlocks();
+    }
+
     if (anyInlinedMethods) {
       // If a method was inlined we may be able to remove check-cast instructions because we may
       // have more information about the types of the arguments at the call site. This is
@@ -259,6 +279,8 @@
           .libraryMethodOptimizer()
           .optimize(code, feedback, methodProcessor, methodProcessingContext);
     }
+
+    assert code.isConsistentSSA(appView);
   }
 
   private EligibilityStatus isClassEligible(DexProgramClass clazz) {
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 51881ef..5365575 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
@@ -375,43 +375,52 @@
       IRCode code,
       AffectedValues affectedValues,
       OptimizationFeedback feedback,
-      InliningIRProvider inliningIRProvider)
+      InliningIRProvider inliningIRProvider,
+      Timing timing)
       throws IllegalClassInlinerStateException {
     // Verify that `eligibleInstance` is not aliased.
     assert eligibleInstance == eligibleInstance.getAliasedValue();
 
-    boolean anyInlinedMethods =
-        forceInlineDirectMethodInvocations(code, feedback, inliningIRProvider);
-    anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, feedback, inliningIRProvider);
+    boolean anyInlinedMethods;
+    try (Timing t0 = timing.begin("Force inline direct methods")) {
+      anyInlinedMethods =
+          forceInlineDirectMethodInvocations(code, feedback, inliningIRProvider, timing);
+    }
+    try (Timing t0 = timing.begin("Force inline indirect methods")) {
+      anyInlinedMethods |=
+          forceInlineIndirectMethodInvocations(code, feedback, inliningIRProvider, timing);
+    }
 
-    rebindIndirectEligibleInstanceUsersFromPhis();
-    removeMiscUsages(code, affectedValues);
-    removeFieldReads(code, affectedValues);
-    removeFieldWrites();
-    removeInstruction(root);
+    try (Timing t0 = timing.begin("Process users")) {
+      rebindIndirectEligibleInstanceUsersFromPhis();
+      removeMiscUsages(code, affectedValues);
+      removeFieldReads(code, affectedValues);
+      removeFieldWrites();
+      removeInstruction(root);
+    }
     return anyInlinedMethods;
   }
 
   @SuppressWarnings("ReferenceEquality")
   private boolean forceInlineDirectMethodInvocations(
-      IRCode code, OptimizationFeedback feedback, InliningIRProvider inliningIRProvider)
+      IRCode code,
+      OptimizationFeedback feedback,
+      InliningIRProvider inliningIRProvider,
+      Timing timing)
       throws IllegalClassInlinerStateException {
     if (directMethodCalls.isEmpty()) {
       return false;
     }
 
+    timing.begin("Inline initial");
     inliner.performForcedInlining(
-        method,
-        code,
-        directMethodCalls,
-        feedback,
-        inliningIRProvider,
-        methodProcessor,
-        Timing.empty());
+        method, code, directMethodCalls, feedback, inliningIRProvider, methodProcessor, timing);
+    timing.end();
 
     // In case we are class inlining an object allocation that does not inherit directly from
     // java.lang.Object, we need keep force inlining the constructor until we reach
     // java.lang.Object.<init>().
+    timing.begin("Inline constructors");
     if (root.isNewInstance()) {
       do {
         directMethodCalls.clear();
@@ -460,17 +469,21 @@
               feedback,
               inliningIRProvider,
               methodProcessor,
-              Timing.empty());
+              timing);
         }
       } while (!directMethodCalls.isEmpty());
     }
+    timing.end();
 
     return true;
   }
 
   @SuppressWarnings("ReferenceEquality")
   private boolean forceInlineIndirectMethodInvocations(
-      IRCode code, OptimizationFeedback feedback, InliningIRProvider inliningIRProvider)
+      IRCode code,
+      OptimizationFeedback feedback,
+      InliningIRProvider inliningIRProvider,
+      Timing timing)
       throws IllegalClassInlinerStateException {
     if (indirectMethodCallsOnInstance.isEmpty()) {
       return false;
@@ -548,7 +561,7 @@
           feedback,
           inliningIRProvider,
           methodProcessor,
-          Timing.empty());
+          timing);
     } else {
       // TODO(b/315284776): Diagnose if this should be removed or reenabled.
       /*assert indirectMethodCallsOnInstance.stream()