Only iterate invokes to inline during force inlining

This leverages the fact that we can now create an instruction iterator at a given instruction in constant time.

Change-Id: I35a400cefc821c38010af424866f0c463a3d14d9
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 5924200..c926852 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1782,7 +1782,7 @@
     return instructions.iterator();
   }
 
-  public InstructionIterator iterator(Instruction instruction) {
+  public BasicBlockInstructionListIterator iterator(Instruction instruction) {
     return instructions.iterator(instruction);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index bba1a6c..995b1bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -66,7 +66,6 @@
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
@@ -79,7 +78,10 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Deque;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -88,6 +90,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -1010,8 +1013,44 @@
       MethodProcessor methodProcessor,
       Timing timing) {
     ForcedInliningOracle oracle = new ForcedInliningOracle(appView, invokesToInline);
+    Map<BasicBlock, List<InvokeMethod>> blockToInvokes = new IdentityHashMap<>();
+    for (InvokeMethod invoke : invokesToInline.keySet()) {
+      blockToInvokes.computeIfAbsent(invoke.getBlock(), ignoreKey(ArrayList::new)).add(invoke);
+    }
+    InvokeSupplier invokeSupplier =
+        (block, consumer) -> {
+          List<InvokeMethod> invokes = blockToInvokes.getOrDefault(block, Collections.emptyList());
+          if (invokes.isEmpty()) {
+            return;
+          }
+          // Sort the invokes according to their order in the block.
+          int instructionIndex = 0;
+          for (Instruction instruction = block.entry();
+              instruction != null;
+              instruction = instruction.getNext(), instructionIndex++) {
+            instruction.setNumber(instructionIndex);
+          }
+          ListUtils.destructiveSort(invokes, Comparator.comparingInt(Instruction::getNumber));
+          // Pass the invokes to the consumer, accounting for the fact that invokes may have been
+          // moved to another block as a result of inlining.
+          for (InvokeMethod invoke : invokes) {
+            BasicBlock invokeBlock = invoke.getBlock();
+            if (invokeBlock == block) {
+              consumer.accept(invoke, block.iterator(invoke.getNext()));
+            } else {
+              blockToInvokes.computeIfAbsent(invokeBlock, ignoreKey(ArrayList::new)).add(invoke);
+            }
+          }
+        };
     performInliningImpl(
-        oracle, method, code, feedback, inliningIRProvider, methodProcessor, timing);
+        oracle,
+        method,
+        code,
+        invokeSupplier,
+        feedback,
+        inliningIRProvider,
+        methodProcessor,
+        timing);
   }
 
   public void performInlining(
@@ -1041,8 +1080,27 @@
     InliningIRProvider inliningIRProvider =
         new InliningIRProvider(appView, method, code, lensCodeRewriter, methodProcessor);
     assert inliningIRProvider.verifyIRCacheIsEmpty();
+    InvokeSupplier invokeSupplier =
+        (block, consumer) -> {
+          InstructionListIterator iterator = block.listIterator();
+          while (iterator.hasNext()) {
+            Instruction current = iterator.next();
+            if (current.isInvokeMethod()) {
+              consumer.accept(current.asInvokeMethod(), iterator);
+            }
+          }
+        };
     performInliningImpl(
-        oracle, method, code, feedback, inliningIRProvider, methodProcessor, timing);
+        oracle,
+        method,
+        code,
+        invokeSupplier,
+        feedback,
+        inliningIRProvider,
+        methodProcessor,
+        timing);
+    code.removeRedundantBlocks();
+    assert code.isConsistentSSA(appView);
   }
 
   public InliningReasonStrategy createDefaultInliningReasonStrategy(
@@ -1067,6 +1125,7 @@
       InliningOracle oracle,
       ProgramMethod context,
       IRCode code,
+      InvokeSupplier invokeSupplier,
       OptimizationFeedback feedback,
       InliningIRProvider inliningIRProvider,
       MethodProcessor methodProcessor,
@@ -1077,7 +1136,6 @@
     ClassInitializationAnalysis classInitializationAnalysis =
         new ClassInitializationAnalysis(appView, code);
     Deque<BasicBlock> inlineeStack = new ArrayDeque<>();
-    InlinerOptions options = appView.options().inlinerOptions();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (!inlineeStack.isEmpty() && inlineeStack.peekFirst() == block) {
@@ -1086,166 +1144,194 @@
       if (blocksToRemove.contains(block)) {
         continue;
       }
-      InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext()) {
-        Instruction current = iterator.next();
-        if (current.isInvokeMethod()) {
-          InvokeMethod invoke = current.asInvokeMethod();
-          // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
-          DexMethod invokedMethod = invoke.getInvokedMethod();
-          SingleResolutionResult<?> resolutionResult =
-              appView
-                  .appInfo()
-                  .resolveMethodLegacy(invokedMethod, invoke.getInterfaceBit())
-                  .asSingleResolution();
-          if (resolutionResult == null
-              || resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
-            continue;
-          }
-
-          if (tryInlineMethodWithoutSideEffects(
-              code, iterator, invoke, resolutionResult.getResolutionPair())) {
-            continue;
-          }
-
-          // TODO(b/156853206): Should not duplicate resolution.
-          ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
-          if (singleTarget == null) {
-            WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(
-                this, invoke, appView, context);
-            continue;
-          }
-
-          InliningOracle singleTargetOracle = getSingleTargetOracle(invoke, singleTarget, oracle);
-          DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
-          WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
-              singleTargetOracle.isForcedInliningOracle()
-                  ? NopWhyAreYouNotInliningReporter.getInstance()
-                  : createWhyAreYouNotInliningReporter(singleTarget, context);
-          timing.begin("Compute inlining");
-          InlineResult inlineResult =
-              singleTargetOracle.computeInlining(
-                  code,
-                  invoke,
-                  resolutionResult,
-                  singleTarget,
+      invokeSupplier.forEachInvoke(
+          block,
+          (invoke, iterator) ->
+              inlineInvoke(
+                  oracle,
                   context,
-                  classInitializationAnalysis,
+                  code,
+                  feedback,
                   inliningIRProvider,
+                  methodProcessor,
                   timing,
-                  whyAreYouNotInliningReporter);
-          timing.end();
-          if (inlineResult == null) {
-            assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
-            continue;
-          }
-
-          if (inlineResult.isRetryAction()) {
-            enqueueMethodForReprocessing(context);
-            continue;
-          }
-
-          InlineAction action = inlineResult.asInlineAction();
-          if (action.reason == Reason.MULTI_CALLER_CANDIDATE) {
-            assert methodProcessor.isPrimaryMethodProcessor();
-            continue;
-          }
-
-          if (!singleTargetOracle.stillHasBudget(action, whyAreYouNotInliningReporter)) {
-            assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
-            continue;
-          }
-
-          timing.begin("Build inlinee");
-          IRCode inlinee = action.buildInliningIR(appView, invoke, context, inliningIRProvider);
-          timing.end();
-          if (singleTargetOracle.willExceedBudget(
-              action, code, inlinee, invoke, block, whyAreYouNotInliningReporter)) {
-            assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
-            continue;
-          }
-
-          // Verify this code went through the full pipeline.
-          assert singleTarget.getDefinition().isProcessed();
-
-          boolean inlineeMayHaveInvokeMethod = inlinee.metadata().mayHaveInvokeMethod();
-
-          // Inline the inlinee code in place of the invoke instruction
-          // Back up before the invoke instruction.
-          iterator.previous();
-
-          // Intentionally not using singleTargetOracle here, so that we decrease the inlining
-          // instruction allowance on the default inlining oracle when force inlining methods.
-          oracle.markInlined(inlinee);
-
-          timing.begin("Inline invoke");
-          iterator.inlineInvoke(
-              appView, code, inlinee, blockIterator, blocksToRemove, action.getDowncastClass());
-          timing.end();
-
-          if (action.shouldEnsureStoreStoreFenceCauses != null) {
-            assert !action.shouldEnsureStoreStoreFenceCauses.isEmpty();
-            assert converter.isInWave();
-            inlinedFinalFieldsInWave.addAll(action.shouldEnsureStoreStoreFenceCauses);
-            scheduleWaveDone();
-          }
-
-          if (methodProcessor.getCallSiteInformation().hasSingleCallSite(singleTarget, context)) {
-            feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
-            appView.withArgumentPropagator(
-                argumentPropagator ->
-                    argumentPropagator.notifyMethodSingleCallerInlined(
-                        singleTarget, context, methodProcessor));
-            if (!(methodProcessor instanceof OneTimeMethodProcessor)) {
-              assert converter.isInWave();
-              scheduleWaveDone();
-              singleCallerInlinedMethodsInWave
-                  .computeIfAbsent(
-                      singleTarget.getHolder(), ignoreKey(ProgramMethodMap::createConcurrent))
-                  .put(singleTarget, context);
-            }
-          }
-
-          methodProcessor.getCallSiteInformation().notifyMethodInlined(context, singleTarget);
-
-          timing.begin("Post process inlinee");
-          classInitializationAnalysis.notifyCodeHasChanged();
-          postProcessInlineeBlocks(
-              code, blockIterator, block, affectedValues, blocksToRemove, timing);
-          timing.end();
-
-          // The synthetic and bridge flags are maintained only if the inlinee has also these flags.
-          if (context.getAccessFlags().isBridge() && !singleTarget.getAccessFlags().isBridge()) {
-            context.getAccessFlags().demoteFromBridge();
-          }
-          if (context.getAccessFlags().isSynthetic()
-              && !singleTarget.getAccessFlags().isSynthetic()) {
-            context.getAccessFlags().demoteFromSynthetic();
-          }
-
-          context.getDefinition().copyMetadata(appView, singleTargetMethod);
-
-          if (inlineeMayHaveInvokeMethod) {
-            int inliningDepth = inlineeStack.size() + 1;
-            if (options.shouldApplyInliningToInlinee(appView, singleTarget, inliningDepth)) {
-              // Record that we will be inside the inlinee until the next block.
-              BasicBlock inlineeEnd = IteratorUtils.peekNext(blockIterator);
-              inlineeStack.push(inlineeEnd);
-              // Move the cursor back to where the first inlinee block was added.
-              IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
-              blockIterator.next();
-            }
-          }
-        }
-      }
+                  blockIterator,
+                  iterator,
+                  invoke,
+                  affectedValues,
+                  blocksToRemove,
+                  classInitializationAnalysis,
+                  inlineeStack));
     }
     assert inlineeStack.isEmpty();
     code.removeBlocks(blocksToRemove);
     classInitializationAnalysis.finish();
     code.removeAllDeadAndTrivialPhis(affectedValues);
     affectedValues.narrowingWithAssumeRemoval(appView, code);
-    code.removeRedundantBlocks();
-    assert code.isConsistentSSA(appView);
+    assert code.isConsistentSSAAllowingRedundantBlocks(appView);
+  }
+
+  private void inlineInvoke(
+      InliningOracle oracle,
+      ProgramMethod context,
+      IRCode code,
+      OptimizationFeedback feedback,
+      InliningIRProvider inliningIRProvider,
+      MethodProcessor methodProcessor,
+      Timing timing,
+      BasicBlockIterator blockIterator,
+      InstructionListIterator iterator,
+      InvokeMethod invoke,
+      AffectedValues affectedValues,
+      Set<BasicBlock> blocksToRemove,
+      ClassInitializationAnalysis classInitializationAnalysis,
+      Deque<BasicBlock> inlineeStack) {
+    // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
+    BasicBlock block = invoke.getBlock();
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    SingleResolutionResult<?> resolutionResult =
+        appView
+            .appInfo()
+            .resolveMethodLegacy(invokedMethod, invoke.getInterfaceBit())
+            .asSingleResolution();
+    if (resolutionResult == null
+        || resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
+      return;
+    }
+
+    if (tryInlineMethodWithoutSideEffects(
+        code, iterator, invoke, resolutionResult.getResolutionPair())) {
+      return;
+    }
+
+    // TODO(b/156853206): Should not duplicate resolution.
+    ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
+    if (singleTarget == null) {
+      WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(this, invoke, appView, context);
+      return;
+    }
+
+    InliningOracle singleTargetOracle = getSingleTargetOracle(invoke, singleTarget, oracle);
+    DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
+    WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
+        singleTargetOracle.isForcedInliningOracle()
+            ? NopWhyAreYouNotInliningReporter.getInstance()
+            : createWhyAreYouNotInliningReporter(singleTarget, context);
+    timing.begin("Compute inlining");
+    InlineResult inlineResult =
+        singleTargetOracle.computeInlining(
+            code,
+            invoke,
+            resolutionResult,
+            singleTarget,
+            context,
+            classInitializationAnalysis,
+            inliningIRProvider,
+            timing,
+            whyAreYouNotInliningReporter);
+    timing.end();
+    if (inlineResult == null) {
+      assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
+      return;
+    }
+
+    if (inlineResult.isRetryAction()) {
+      enqueueMethodForReprocessing(context);
+      return;
+    }
+
+    InlineAction action = inlineResult.asInlineAction();
+    if (action.reason == Reason.MULTI_CALLER_CANDIDATE) {
+      assert methodProcessor.isPrimaryMethodProcessor();
+      return;
+    }
+
+    if (!singleTargetOracle.stillHasBudget(action, whyAreYouNotInliningReporter)) {
+      assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
+      return;
+    }
+
+    timing.begin("Build inlinee");
+    IRCode inlinee = action.buildInliningIR(appView, invoke, context, inliningIRProvider);
+    timing.end();
+    if (singleTargetOracle.willExceedBudget(
+        action, code, inlinee, invoke, block, whyAreYouNotInliningReporter)) {
+      assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
+      return;
+    }
+
+    // Verify this code went through the full pipeline.
+    assert singleTarget.getDefinition().isProcessed();
+
+    boolean inlineeMayHaveInvokeMethod = inlinee.metadata().mayHaveInvokeMethod();
+
+    // Inline the inlinee code in place of the invoke instruction
+    // Back up before the invoke instruction.
+    iterator.previous();
+
+    // Intentionally not using singleTargetOracle here, so that we decrease the inlining
+    // instruction allowance on the default inlining oracle when force inlining methods.
+    oracle.markInlined(inlinee);
+
+    timing.begin("Inline invoke");
+    iterator.inlineInvoke(
+        appView, code, inlinee, blockIterator, blocksToRemove, action.getDowncastClass());
+    timing.end();
+
+    if (action.shouldEnsureStoreStoreFenceCauses != null) {
+      assert !action.shouldEnsureStoreStoreFenceCauses.isEmpty();
+      assert converter.isInWave();
+      inlinedFinalFieldsInWave.addAll(action.shouldEnsureStoreStoreFenceCauses);
+      scheduleWaveDone();
+    }
+
+    if (methodProcessor.getCallSiteInformation().hasSingleCallSite(singleTarget, context)) {
+      feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
+      appView.withArgumentPropagator(
+          argumentPropagator ->
+              argumentPropagator.notifyMethodSingleCallerInlined(
+                  singleTarget, context, methodProcessor));
+      if (!(methodProcessor instanceof OneTimeMethodProcessor)) {
+        assert converter.isInWave();
+        scheduleWaveDone();
+        singleCallerInlinedMethodsInWave
+            .computeIfAbsent(
+                singleTarget.getHolder(), ignoreKey(ProgramMethodMap::createConcurrent))
+            .put(singleTarget, context);
+      }
+    }
+
+    methodProcessor.getCallSiteInformation().notifyMethodInlined(context, singleTarget);
+
+    timing.begin("Post process inlinee");
+    classInitializationAnalysis.notifyCodeHasChanged();
+    postProcessInlineeBlocks(code, blockIterator, block, affectedValues, blocksToRemove, timing);
+    timing.end();
+
+    // The synthetic and bridge flags are maintained only if the inlinee has also these flags.
+    if (context.getAccessFlags().isBridge() && !singleTarget.getAccessFlags().isBridge()) {
+      context.getAccessFlags().demoteFromBridge();
+    }
+    if (context.getAccessFlags().isSynthetic() && !singleTarget.getAccessFlags().isSynthetic()) {
+      context.getAccessFlags().demoteFromSynthetic();
+    }
+
+    context.getDefinition().copyMetadata(appView, singleTargetMethod);
+
+    if (inlineeMayHaveInvokeMethod) {
+      int inliningDepth = inlineeStack.size() + 1;
+      if (appView
+          .options()
+          .inlinerOptions()
+          .shouldApplyInliningToInlinee(appView, singleTarget, inliningDepth)) {
+        // Record that we will be inside the inlinee until the next block.
+        BasicBlock inlineeEnd = IteratorUtils.peekNext(blockIterator);
+        inlineeStack.push(inlineeEnd);
+        // Move the cursor back to where the first inlinee block was added.
+        IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
+        blockIterator.next();
+      }
+    }
   }
 
   private InliningOracle getSingleTargetOracle(
@@ -1483,4 +1569,10 @@
     }
     return true;
   }
+
+  private interface InvokeSupplier {
+
+    void forEachInvoke(
+        BasicBlock block, BiConsumer<InvokeMethod, InstructionListIterator> consumer);
+  }
 }