Fix move scheduler issue and minimize reproduction

This fixes an issue where the dst registers of a move is used as a temporary register for the src registers of the same move.

Change-Id: I5fa0d9635bbc5fcf30bd9b0277ef7e791dbec7b4
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index 70d2ab8..7cf3dfb 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -87,6 +87,48 @@
         || (isWide() && scheduler.activeTempRegisters.contains(dst + 1));
   }
 
+  // In order to unblock another move, the src register of a move can be moved to a temporary
+  // register. When the move is wide, the src register may be moved to one of its own destination
+  // registers.
+  //
+  // Example: Consider `move-wide r1, r2 <- r4, r5`. If one of the src registers r4, r5 are blocking
+  // another move, we need to move r4, r5 to a temp. Since the current move-wide has not already
+  // been scheduled, it must be the case that it is itself blocked, i.e., some other move is reading
+  // either r1, r2, or both.
+  //
+  // If both r1, r2 are read by other moves, then r1, r2 cannot be used as temporary registers, and
+  // there is no issue.
+  //
+  // If r1 is read by another move, but r2 is not, then we can use r2 as a temporary register. Thus
+  // to unblock another move, we might create the temporary move `move-wide r2, r3 <- r4, r5`. A
+  // prerequisite for this is that there exists a move `r3 <- _` and that `r3` is not read by any
+  // moves in the move set, so that r3 can also be used as a temporary register.
+  //
+  // (Similarly, we could create the temporary move `move-wide r0, r1 <- r4, r5`.)
+  //
+  // This leads to a situation where a move is blocking itself, because it is using one of its own
+  // registers as a temporary register to unblock another move.
+  public boolean isDestUsedAsTemporaryForSelf(RegisterMoveScheduler scheduler) {
+    assert isDestUsedAsTemporary(scheduler);
+    if (!isWide()) {
+      return false;
+    }
+    // We don't move constants to temporary registers.
+    if (definition != null) {
+      assert src == NO_REGISTER;
+      return false;
+    }
+    int mappedSrc = scheduler.valueMap.get(src);
+    assert mappedSrc != dst;
+    if (mappedSrc == dst - 1 && scheduler.activeTempRegisters.contains(dst)) {
+      return true;
+    }
+    if (mappedSrc == dst + 1 && scheduler.activeTempRegisters.contains(dst + 1)) {
+      return true;
+    }
+    return false;
+  }
+
   public boolean isIdentical(RegisterMove move) {
     return ObjectUtils.identical(this, move);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index ea004c3..b71ea38 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -39,7 +39,7 @@
   private final TreeSet<RegisterMove> moveSet = new TreeSet<>();
   // Mapping to keep track of which values currently corresponds to each other.
   // This is initially an identity map but changes as we insert moves.
-  private final Int2IntMap valueMap = new Int2IntOpenHashMap();
+  final Int2IntMap valueMap = new Int2IntOpenHashMap();
   // Location at which to insert the scheduled moves.
   private final InstructionListIterator insertAt;
   // Debug position associated with insertion point.
@@ -56,7 +56,7 @@
   // Registers that are the destination register of some move in the move set, but which are
   // currently being used as a temporary register for another value. Moves in the move set that
   // write a register in this set are blocked until the temporary registers are released.
-  public final IntSet activeTempRegisters = new IntOpenHashSet();
+  final IntSet activeTempRegisters = new IntOpenHashSet();
 
   public RegisterMoveScheduler(
       InstructionListIterator insertAt,
@@ -141,7 +141,8 @@
       while (!worklist.isEmpty()) {
         while (!worklist.isEmpty()) {
           RegisterMove move = worklist.removeFirst();
-          assert !move.isBlocked(this, moveSet, valueMap);
+          assert !move.isBlocked(this, moveSet, valueMap)
+              || move.isDestUsedAsTemporaryForSelf(this);
           createMove(move);
         }
         enqueueUnblockedMoves(worklist, movesToSchedule);
@@ -253,7 +254,10 @@
     // In order to unblock this move we might have to move more than one value to temporary
     // registers if we are unlucky with the overlap for values that use two registers.
     List<RegisterMove> movesWithSrc = findMovesWithSrc(move.dst, move.type);
-    assert movesWithSrc.size() > 0;
+    if (movesWithSrc.isEmpty()) {
+      assert move.isDestUsedAsTemporaryForSelf(this);
+      return;
+    }
     assert verifyMovesHaveDifferentSources(movesWithSrc);
     for (RegisterMove moveWithSrc : movesWithSrc) {
       // TODO(b/375147902): Maybe seed the move scheduler with a set of registers known to be free
@@ -385,7 +389,9 @@
   private RegisterMove pickMoveToUnblock(TreeSet<RegisterMove> moves) {
     // Pick a non-wide move to unblock if possible.
     Iterable<RegisterMove> eligible =
-        Iterables.filter(moves, move -> !move.isDestUsedAsTemporary(this));
+        Iterables.filter(
+            moves,
+            move -> !move.isDestUsedAsTemporary(this) || move.isDestUsedAsTemporaryForSelf(this));
     RegisterMove move =
         Iterables.find(eligible, not(RegisterMove::isWide), eligible.iterator().next());
     moves.remove(move);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 5d57ddb..de6c3c6 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.ir.regalloc;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -40,7 +39,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.UnaryOperator;
@@ -61,6 +59,10 @@
       return list.get(i).asMove();
     }
 
+    public ConstNumber getConst(int i) {
+      return list.get(i).asConstNumber();
+    }
+
     public int size() {
       return list.size();
     }
@@ -589,37 +591,29 @@
   @Test
   public void reproNoSuchElementException() {
     CollectMovesIterator moves = new CollectMovesIterator();
-    int temp = 53;
-    int args = 1;
-    RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp, args);
-    scheduler.addMove(new RegisterMove(23, 15, TypeElement.getInt()));
-    scheduler.addMove(new RegisterMove(26, 14, TypeElement.getInt()));
-    scheduler.addMove(new RegisterMove(15, 20, TypeElement.getLong()));
-    scheduler.addMove(new RegisterMove(17, 27, TypeElement.getLong()));
-    scheduler.addMove(new RegisterMove(19, 4, TypeElement.getLong()));
-    scheduler.addMove(new RegisterMove(21, 16, TypeElement.getLong()));
-    scheduler.addMove(new RegisterMove(24, 6, TypeElement.getInt()));
-    scheduler.addMove(new RegisterMove(25, 7, TypeElement.getInt()));
-    scheduler.addMove(new RegisterMove(14, 8, TypeElement.getInt()));
-    scheduler.addMove(new RegisterMove(30, 9, TypeElement.getInt()));
-    scheduler.addMove(new RegisterMove(31, 13, TypeElement.getInt()));
-    scheduler.addMove(new RegisterMove(34, 1, TypeElement.getInt()));
+    int temp = 42;
+    RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
+    scheduler.addMove(new RegisterMove(10, 2, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(2, 7, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(4, 11, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(6, 0, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(8, 3, TypeElement.getLong()));
     scheduler.addMove(
         new RegisterMove(
-            27,
+            11,
             TypeElement.getInt(),
-            new ConstNumber(new Value(0, TypeElement.getInt(), null), 3)));
-    scheduler.addMove(
-        new RegisterMove(
-            28,
-            TypeElement.getInt(),
-            new ConstNumber(new Value(1, TypeElement.getInt(), null), 18)));
-    try {
-      scheduler.schedule();
-      fail();
-    } catch (NoSuchElementException e) {
-      // Expected.
-    }
+            new ConstNumber(new Value(0, TypeElement.getInt(), null), 0)));
+    scheduler.schedule();
+    assertEquals(8, moves.size());
+    assertEquals("10 <- 2", toString(moves.get(0)));
+    assertEquals("5 <- 11", toString(moves.get(1)));
+    assertEquals("11 <- const 0", toString(moves.getConst(2)));
+    assertEquals("42 <- 7", toString(moves.get(3)));
+    assertEquals("8 <- 3", toString(moves.get(4)));
+    assertEquals("2 <- 42", toString(moves.get(5)));
+    assertEquals("4 <- 5", toString(moves.get(6)));
+    assertEquals("6 <- 0", toString(moves.get(7)));
+    assertEquals(2, scheduler.getUsedTempRegisters());
   }
 
   // Debugging aid.
@@ -633,7 +627,13 @@
     System.out.println("----------------");
   }
 
-  private String toString(Instruction move) {
+  private String toString(ConstNumber move) {
+    return move.outValue().asFixedRegisterValue().getRegister()
+        + " <- const "
+        + move.asConstNumber().getRawValue();
+  }
+
+  private String toString(Move move) {
     return move.outValue().asFixedRegisterValue().getRegister()
         + " <- "
         + move.getFirstOperand().asFixedRegisterValue().getRegister();