Avoid use of LinkedList get in debug position removal

These removes quadratic behavior but did not show a noticeable
performance impact on Tivi when tested locally.

Change-Id: I844f2726e4221b6e0f9f86cabf14ebc2c1d81cc6
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 60a7c0c..7f7653b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -529,6 +529,29 @@
     }
   }
 
+  public interface BasicBlockIteratorCallback {
+
+    /**
+     * @param current Current block. Always non-null.
+     * @param previous Null if current block is the entry block, otherwise previous in iteration.
+     * @param next Null if current block is the last block, otherwise next in iteration.
+     */
+    void accept(BasicBlock current, BasicBlock previous, BasicBlock next);
+  }
+
+  public void forEachBlockWithPreviousAndNext(BasicBlockIteratorCallback callback) {
+    assert !blocks.isEmpty();
+    BasicBlockIterator blockIterator = listIterator();
+    BasicBlock previousBlock = null;
+    BasicBlock currentBlock = blockIterator.next();
+    do {
+      BasicBlock nextBlock = blockIterator.hasNext() ? blockIterator.next() : null;
+      callback.accept(currentBlock, previousBlock, nextBlock);
+      previousBlock = currentBlock;
+      currentBlock = nextBlock;
+    } while (currentBlock != null);
+  }
+
   /**
    * Compute quasi topologically sorted list of the basic blocks using depth first search.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 8c6c16a..3dadbc0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -53,6 +53,7 @@
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCode.BasicBlockIteratorCallback;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -451,107 +452,113 @@
     // determining if a goto will materialize or not.
     removeTrivialGotoBlocks(code);
 
-    // Current position known to have a materializing instruction associated with it.
-    Position currentMaterializedPosition = Position.none();
-
-    // Current debug-position marker that is not yet known to have another instruction materializing
-    // to the same position.
-    DebugPosition unresolvedPosition = null;
-
-    // Locals live at the debug-position marker. These must also be the same at a possible
-    // materializing instruction with the same position for it to be sound to remove the marker.
-    Int2ReferenceMap<DebugLocalInfo> localsAtUnresolvedPosition = null;
-
     // Compute the set of all positions that can be removed.
     // (Delaying removal to avoid ConcurrentModificationException).
     List<DebugPosition> toRemove = new ArrayList<>();
 
-    for (int blockIndex = 0; blockIndex < code.blocks.size(); blockIndex++) {
-      BasicBlock currentBlock = code.blocks.get(blockIndex);
+    code.forEachBlockWithPreviousAndNext(
+        new BasicBlockIteratorCallback() {
 
-      // Current materialized position must be updated to the position we can guarantee is emitted
-      // in all predecessors. The position of a fallthrough predecessor is defined by
-      // currentMaterializedPosition and unresolvedPosition (and not by the position of its exit!)
-      // If this is the entry block or a trivial fall-through with no other predecessors the
-      // materialized and unresolved positions remain unchanged.
-      if (blockIndex != 0) {
-        BasicBlock previousBlock = code.blocks.get(blockIndex - 1);
-        if (!isTrivialFallthroughTarget(previousBlock, currentBlock)) {
-          Position positionAtAllPredecessors = null;
-          for (BasicBlock pred : currentBlock.getPredecessors()) {
-            Position predExit;
-            if (pred == previousBlock) {
-              predExit =
-                  unresolvedPosition != null
-                      ? unresolvedPosition.getPosition()
-                      : currentMaterializedPosition;
-            } else {
-              predExit = pred.exit().getPosition();
-            }
-            if (positionAtAllPredecessors == null) {
-              positionAtAllPredecessors = predExit;
-            } else if (!positionAtAllPredecessors.equals(predExit)) {
-              positionAtAllPredecessors = Position.none();
-              break;
-            }
-          }
-          unresolvedPosition = null;
-          currentMaterializedPosition = positionAtAllPredecessors;
-        }
-      }
+          // Current position known to have a materializing instruction associated with it.
+          Position currentMaterializedPosition = Position.none();
 
-      // Current locals.
-      Int2ReferenceMap<DebugLocalInfo> locals =
-          currentBlock.getLocalsAtEntry() != null
-              ? new Int2ReferenceOpenHashMap<>(currentBlock.getLocalsAtEntry())
-              : new Int2ReferenceOpenHashMap<>();
+          // Current debug-position marker that is not yet known to have another instruction
+          // materializing
+          // to the same position.
+          DebugPosition unresolvedPosition = null;
 
-      // Next block to decide which gotos are fall-throughs.
-      BasicBlock nextBlock =
-          blockIndex + 1 < code.blocks.size() ? code.blocks.get(blockIndex + 1) : null;
+          // Locals live at the debug-position marker. These must also be the same at a possible
+          // materializing instruction with the same position for it to be sound to remove the
+          // marker.
+          Int2ReferenceMap<DebugLocalInfo> localsAtUnresolvedPosition = null;
 
-      for (com.android.tools.r8.ir.code.Instruction instruction : currentBlock.getInstructions()) {
-        if (instruction.isDebugPosition()) {
-          if (unresolvedPosition == null
-              && currentMaterializedPosition == instruction.getPosition()) {
-            // Here we don't need to check locals state as the line is already active.
-            toRemove.add(instruction.asDebugPosition());
-            assert currentBlock.size() != 2
-                    || currentBlock.exit().getPosition() != currentMaterializedPosition
-                    || !currentBlock.exit().isGoto()
-                    || currentBlock.exit().asGoto().getTarget() != nextBlock
-                : "Unexpected trivial fallthrough block. This should be removed already.";
-          } else if (unresolvedPosition != null
-              && unresolvedPosition.getPosition() == instruction.getPosition()
-              && locals.equals(localsAtUnresolvedPosition)) {
-            // toRemove needs to be in instruction iteration order since the removal assumes that.
-            // Therefore, we have to remove unresolvedPosition here and record the current
-            // instruction as unresolved. Otherwise, if both of these instructions end up in
-            // toRemove they will be out of order.
-            toRemove.add(unresolvedPosition);
-            unresolvedPosition = instruction.asDebugPosition();
-          } else {
-            unresolvedPosition = instruction.asDebugPosition();
-            localsAtUnresolvedPosition = new Int2ReferenceOpenHashMap<>(locals);
-          }
-        } else {
-          assert instruction.getPosition().isSome();
-          if (instruction.isDebugLocalsChange()) {
-            instruction.asDebugLocalsChange().apply(locals);
-          } else if (!isNopInstruction(instruction, nextBlock)) {
-            if (unresolvedPosition != null) {
-              if (unresolvedPosition.getPosition() == instruction.getPosition()
-                  && locals.equals(localsAtUnresolvedPosition)) {
-                toRemove.add(unresolvedPosition);
+          @Override
+          public void accept(
+              BasicBlock currentBlock, BasicBlock previousBlock, BasicBlock nextBlock) {
+            // Current materialized position must be updated to the position we can guarantee is
+            // emitted
+            // in all predecessors. The position of a fallthrough predecessor is defined by
+            // currentMaterializedPosition and unresolvedPosition (and not by the position of its
+            // exit!)
+            // If this is the entry block or a trivial fall-through with no other predecessors the
+            // materialized and unresolved positions remain unchanged.
+            if (previousBlock != null) {
+              if (!isTrivialFallthroughTarget(previousBlock, currentBlock)) {
+                Position positionAtAllPredecessors = null;
+                for (BasicBlock pred : currentBlock.getPredecessors()) {
+                  Position predExit;
+                  if (pred == previousBlock) {
+                    predExit =
+                        unresolvedPosition != null
+                            ? unresolvedPosition.getPosition()
+                            : currentMaterializedPosition;
+                  } else {
+                    predExit = pred.exit().getPosition();
+                  }
+                  if (positionAtAllPredecessors == null) {
+                    positionAtAllPredecessors = predExit;
+                  } else if (!positionAtAllPredecessors.equals(predExit)) {
+                    positionAtAllPredecessors = Position.none();
+                    break;
+                  }
+                }
+                unresolvedPosition = null;
+                currentMaterializedPosition = positionAtAllPredecessors;
               }
-              unresolvedPosition = null;
-              localsAtUnresolvedPosition = null;
             }
-            currentMaterializedPosition = instruction.getPosition();
+
+            // Current locals.
+            Int2ReferenceMap<DebugLocalInfo> locals =
+                currentBlock.getLocalsAtEntry() != null
+                    ? new Int2ReferenceOpenHashMap<>(currentBlock.getLocalsAtEntry())
+                    : new Int2ReferenceOpenHashMap<>();
+
+            for (com.android.tools.r8.ir.code.Instruction instruction :
+                currentBlock.getInstructions()) {
+              if (instruction.isDebugPosition()) {
+                if (unresolvedPosition == null
+                    && currentMaterializedPosition == instruction.getPosition()) {
+                  // Here we don't need to check locals state as the line is already active.
+                  toRemove.add(instruction.asDebugPosition());
+                  assert currentBlock.size() != 2
+                          || currentBlock.exit().getPosition() != currentMaterializedPosition
+                          || !currentBlock.exit().isGoto()
+                          || currentBlock.exit().asGoto().getTarget() != nextBlock
+                      : "Unexpected trivial fallthrough block. This should be removed already.";
+                } else if (unresolvedPosition != null
+                    && unresolvedPosition.getPosition() == instruction.getPosition()
+                    && locals.equals(localsAtUnresolvedPosition)) {
+                  // toRemove needs to be in instruction iteration order since the removal assumes
+                  // that.
+                  // Therefore, we have to remove unresolvedPosition here and record the current
+                  // instruction as unresolved. Otherwise, if both of these instructions end up in
+                  // toRemove they will be out of order.
+                  toRemove.add(unresolvedPosition);
+                  unresolvedPosition = instruction.asDebugPosition();
+                } else {
+                  unresolvedPosition = instruction.asDebugPosition();
+                  localsAtUnresolvedPosition = new Int2ReferenceOpenHashMap<>(locals);
+                }
+              } else {
+                assert instruction.getPosition().isSome();
+                if (instruction.isDebugLocalsChange()) {
+                  instruction.asDebugLocalsChange().apply(locals);
+                } else if (!isNopInstruction(instruction, nextBlock)) {
+                  if (unresolvedPosition != null) {
+                    if (unresolvedPosition.getPosition() == instruction.getPosition()
+                        && locals.equals(localsAtUnresolvedPosition)) {
+                      toRemove.add(unresolvedPosition);
+                    }
+                    unresolvedPosition = null;
+                    localsAtUnresolvedPosition = null;
+                  }
+                  currentMaterializedPosition = instruction.getPosition();
+                }
+              }
+            }
           }
-        }
-      }
-    }
+        });
+
     // Remove all unneeded positions.
     if (!toRemove.isEmpty()) {
       InstructionListIterator it = code.instructionListIterator();
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index a8cdc56..f7c60a4 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -33,6 +33,13 @@
     variants.add(benchmark);
   }
 
+  public List<BenchmarkIdentifier> getBenchmarkIdentifiers() {
+    return benchmarks.values().stream()
+        .flatMap(cs -> cs.stream().map(BenchmarkConfig::getIdentifier))
+        .sorted()
+        .collect(Collectors.toList());
+  }
+
   public BenchmarkConfig getBenchmark(BenchmarkIdentifier identifier) {
     assert identifier != null;
     List<BenchmarkConfig> configs = benchmarks.getOrDefault(identifier.getName(), emptyList());
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
index d5f84a8..a0aa782 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkMainEntryRunner.java
@@ -9,6 +9,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.stream.Collectors;
 import org.junit.rules.TemporaryFolder;
 
 public class BenchmarkMainEntryRunner {
@@ -28,7 +29,13 @@
     BenchmarkCollection collection = BenchmarkCollection.computeCollection();
     BenchmarkConfig config = collection.getBenchmark(identifier);
     if (config == null) {
-      throw new RuntimeException("Unknown identifier: " + identifier);
+      throw new RuntimeException(
+          "Unknown identifier: "
+              + identifier
+              + "\nPossible benchmarks:\n"
+              + collection.getBenchmarkIdentifiers().stream()
+                  .map(BenchmarkIdentifier::toString)
+                  .collect(Collectors.joining("\n")));
     }
 
     TemporaryFolder temp = new TemporaryFolder();