Improve outliner by saving lookups on linked list.

Also enable outliner in JumboStringTest.

Local results (time):

JumboStringsTest  w/o outliner: 0:06
       before CL with outliner: 1:10
        after CL with outliner: 0:20

GmsCore/R8 not affected by CL (~1:10)

Bug: 64777953
Change-Id: I522a38e0a5e6371bcf4ccfa639a82ff813dafc13
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 5bf6e8b..20c54f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.code.Div;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -104,14 +105,13 @@
 
     // Build an outline over the instructions [start, end[.
     // The arguments are the arguments to pass to an outline of these instructions.
-    Outline(BasicBlock block, List<Value> arguments, List<DexType> argumentTypes,
+    Outline(List<Instruction> instructions, List<Value> arguments, List<DexType> argumentTypes,
         List<Integer> argumentMap, DexType returnType, int start, int end) {
       this.arguments = arguments;
       this.argumentTypes = argumentTypes;
       this.argumentMap = argumentMap;
       this.returnType = returnType;
 
-      List<Instruction> instructions = block.getInstructions();
       for (int i = start; i < end; i++) {
         Instruction current = instructions.get(i);
         if (current.isInvoke() || current.isNewInstance() || current.isArithmeticBinop()) {
@@ -300,7 +300,8 @@
 
     final DexEncodedMethod method;
     final BasicBlock block;
-    final List<Instruction> instructions;
+    // instructionArrayCache is block.getInstructions() copied to an ArrayList.
+    private List<Instruction> instructionArrayCache = null;
 
     int start;
     int index;
@@ -317,15 +318,29 @@
     OutlineSpotter(DexEncodedMethod method, BasicBlock block) {
       this.method = method;
       this.block = block;
-      this.instructions = block.getInstructions();
       reset(0);
     }
 
+    protected List<Instruction> getInstructionArray() {
+      if (instructionArrayCache == null) {
+        instructionArrayCache = new ArrayList<>(block.getInstructions());
+      }
+      return instructionArrayCache;
+    }
+
+    // Call this before modifying block.getInstructions().
+    protected void invalidateInstructionArray() {
+      instructionArrayCache = null;
+    }
+
     protected void process() {
-      List<Instruction> instructions = block.getInstructions();
-      while (index < instructions.size()) {
-        Instruction current = instructions.get(index);
-        processInstruction(current);
+      List<Instruction> instructions;
+      for (;;) {
+        instructions = getInstructionArray(); // ProcessInstruction may have invalidated it.
+        if (index >= instructions.size()) {
+          break;
+        }
+        processInstruction(instructions.get(index));
       }
     }
 
@@ -475,9 +490,10 @@
         assert index > 0;
         int offset = 0;
         Instruction previous;
+        List<Instruction> instructions = getInstructionArray();
         do {
           offset++;
-          previous = block.getInstructions().get(index - offset);
+          previous = instructions.get(index - offset);
         } while (previous.isConstInstruction());
         if (!previous.isNewInstance() || previous.outValue() != returnValue) {
           return false;
@@ -539,12 +555,12 @@
               } else {
                 DexProto methodProto;
                 if (instruction.asInvoke().getType() == Type.POLYMORPHIC) {
-                  // Type of argument of a polymorphic call must be take from the call site
+                  // Type of argument of a polymorphic call must be take from the call site.
                   methodProto = instruction.asInvokePolymorphic().getProto();
                 } else {
                   methodProto = invoke.getInvokedMethod().proto;
                 }
-                // -1 due to receiver
+                // -1 due to receiver.
                 argumentTypes.add(methodProto.parameters.values[i - 1]);
               }
               argumentsMap.add(arguments.size() - 1);
@@ -591,6 +607,7 @@
     protected abstract void handle(int start, int end, Outline outline);
 
     private void candidate(int start, int index) {
+      List<Instruction> instructions = getInstructionArray();
       assert !instructions.get(start).isConstInstruction();
 
       if (pendingNewInstanceIndex != -1) {
@@ -621,7 +638,7 @@
       }
 
       Outline outline = new Outline(
-          block, arguments, argumentTypes, argumentsMap, returnType, start, end);
+          instructions, arguments, argumentTypes, argumentsMap, returnType, start, end);
       handle(start, end, outline);
 
       // Start a new candidate search from the next instruction after this outline.
@@ -681,35 +698,37 @@
       if (candidates.containsKey(outline)) {
         DexMethod m = generatedOutlines.get(outline);
         assert m != null;
-        List<Instruction> instructions = block.getInstructions();
         List<Value> in = new ArrayList<>();
         returnValue = null;
         argumentsMapIndex = 0;
-        for (int i = start; i < end; i++) {
-          Instruction current = instructions.get(i);
-          if (current.isConstInstruction()) {
-            // Leave any const instructions.
-            continue;
-          }
-
-          // Prepare to remove the instruction.
-          List<Value> inValues = orderedInValues(current, returnValue);
-          for (int j = 0; j < inValues.size(); j++) {
-            Value value = inValues.get(j);
-            value.removeUser(current);
-            int argumentIndex = outline.argumentMap.get(argumentsMapIndex++);
-            if (argumentIndex >= in.size()) {
-              assert argumentIndex == in.size();
-              in.add(value);
+        { // Scope for 'instructions'.
+          List<Instruction> instructions = getInstructionArray();
+          for (int i = start; i < end; i++) {
+            Instruction current = instructions.get(i);
+            if (current.isConstInstruction()) {
+              // Leave any const instructions.
+              continue;
             }
-          }
-          if (current.outValue() != null) {
-            returnValue = current.outValue();
-          }
-          // The invoke of the outline method will be placed at the last instruction index,
-          // so don't mark that for removal.
-          if (i < end - 1) {
-            toRemove.add(i);
+
+            // Prepare to remove the instruction.
+            List<Value> inValues = orderedInValues(current, returnValue);
+            for (int j = 0; j < inValues.size(); j++) {
+              Value value = inValues.get(j);
+              value.removeUser(current);
+              int argumentIndex = outline.argumentMap.get(argumentsMapIndex++);
+              if (argumentIndex >= in.size()) {
+                assert argumentIndex == in.size();
+                in.add(value);
+              }
+            }
+            if (current.outValue() != null) {
+              returnValue = current.outValue();
+            }
+            // The invoke of the outline method will be placed at the last instruction index,
+            // so don't mark that for removal.
+            if (i < end - 1) {
+              toRemove.add(i);
+            }
           }
         }
         assert m.proto.shorty.toString().length() - 1 == in.size();
@@ -718,13 +737,15 @@
         }
         Invoke outlineInvoke = new InvokeStatic(m, returnValue, in);
         outlineInvoke.setBlock(block);
-        instructions.get(end - 1).clearBlock();
-        instructions.set(end - 1, outlineInvoke);
+        InstructionListIterator endIterator = block.listIterator(end - 1);
+        Instruction instructionBeforeEnd = endIterator.next();
+        invalidateInstructionArray(); // Because we're about to modify the original linked list.
+        instructionBeforeEnd.clearBlock();
+        endIterator.set(outlineInvoke); // Replaces instructionBeforeEnd.
         if (block.hasCatchHandlers()) {
           // If the inserted invoke is inserted in a block with handlers, split the block after
           // the inserted invoke.
-          // TODO(sgjesse): Integrate the use of an instruction iterator into the code above.
-          block.listIterator(end).split(code, blocksIterator);
+          endIterator.split(code, blocksIterator);
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index a52c68b..56c041c 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -53,9 +53,6 @@
     );
 
     InternalOptions options = new InternalOptions();
-    // (b/64777953) Disable outliner optimization for this test because it increases time
-    // from 1 minute to 7 minutes.
-    options.outline.enabled = false;
     DexApplication originalApplication = buildApplication(smaliBuilder, options);
     DexApplication processedApplication = processApplication(originalApplication, options);
     String result = runArt(processedApplication, options);