Use destination registers as temporary registers in move scheduler

Bug: b/375147902
Change-Id: I1ba66bb9ef4fdfb821163614a54630ccd975f5cf
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 963bc1f..70d2ab8 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
@@ -11,6 +11,7 @@
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.IntConsumer;
 
 // Register moves used by the spilling register allocator. These are used both for spill and
 // for phi moves and they are moves between actual registers represented by their register number.
@@ -36,6 +37,22 @@
     this.type = type;
   }
 
+  public void forEachDestinationRegister(IntConsumer consumer) {
+    consumer.accept(dst);
+    if (isWide()) {
+      consumer.accept(dst + 1);
+    }
+  }
+
+  public void forEachSourceRegister(IntConsumer consumer) {
+    if (src != NO_REGISTER) {
+      consumer.accept(src);
+      if (isWide()) {
+        consumer.accept(src + 1);
+      }
+    }
+  }
+
   public boolean writes(int register, boolean otherIsWide) {
     if (dst == register) {
       return true;
@@ -49,7 +66,11 @@
     return false;
   }
 
-  public boolean isBlocked(Set<RegisterMove> moveSet, Int2IntMap valueMap) {
+  public boolean isBlocked(
+      RegisterMoveScheduler scheduler, Set<RegisterMove> moveSet, Int2IntMap valueMap) {
+    if (isDestUsedAsTemporary(scheduler)) {
+      return true;
+    }
     for (RegisterMove move : moveSet) {
       if (isIdentical(move) || move.src == NO_REGISTER) {
         continue;
@@ -61,10 +82,19 @@
     return false;
   }
 
+  public boolean isDestUsedAsTemporary(RegisterMoveScheduler scheduler) {
+    return scheduler.activeTempRegisters.contains(dst)
+        || (isWide() && scheduler.activeTempRegisters.contains(dst + 1));
+  }
+
   public boolean isIdentical(RegisterMove move) {
     return ObjectUtils.identical(this, move);
   }
 
+  public boolean isNotIdentical(RegisterMove move) {
+    return !isIdentical(move);
+  }
+
   public boolean isWide() {
     return type.isWidePrimitive();
   }
@@ -113,4 +143,16 @@
     }
     return definition.getNumber() - o.definition.getNumber();
   }
+
+  @Override
+  public String toString() {
+    if (type.isSinglePrimitive()) {
+      return "move " + dst + ", " + src;
+    } else if (type.isWidePrimitive()) {
+      return "move-wide " + dst + ", " + src;
+    } else {
+      assert type.isReferenceType();
+      return "move-object " + dst + ", " + src;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycle.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycle.java
new file mode 100644
index 0000000..a1e986d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycle.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.regalloc;
+
+import java.util.TreeSet;
+
+class RegisterMoveCycle {
+
+  private final TreeSet<RegisterMove> moves;
+
+  // Whether the cycle is closed, i.e., the moves in the cycle are only blocked by moves also
+  // present in this cycle.
+  private final boolean closed;
+
+  RegisterMoveCycle(TreeSet<RegisterMove> cycle, boolean closed) {
+    this.moves = cycle;
+    this.closed = closed;
+  }
+
+  public TreeSet<RegisterMove> getMoves() {
+    return moves;
+  }
+
+  public boolean isClosed() {
+    return closed;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycleDetector.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycleDetector.java
new file mode 100644
index 0000000..cdb321d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveCycleDetector.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.regalloc;
+
+import com.android.tools.r8.utils.SetUtils;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class RegisterMoveCycleDetector {
+
+  @SuppressWarnings("MixedMutabilityReturnType")
+  static List<RegisterMoveCycle> getMoveCycles(TreeSet<RegisterMove> moveSet) {
+    // Although there can be a cycle when there are two moves, we return no cycles, since the
+    // default move scheduling has the same behavior as the cycle-based move scheduling in this
+    // case.
+    if (moveSet.size() <= 2) {
+      return Collections.emptyList();
+    }
+    List<RegisterMoveCycle> moveCycles = new ArrayList<>();
+    Int2ObjectMap<TreeSet<RegisterMove>> readBy = createReadByGraph(moveSet);
+    Set<RegisterMove> finished = new HashSet<>();
+    Deque<RegisterMove> stack = new ArrayDeque<>();
+    TreeSet<RegisterMove> stackSet = new TreeSet<>();
+    for (RegisterMove move : moveSet) {
+      if (finished.contains(move)) {
+        continue;
+      }
+      dfs(move, finished, readBy, stack, stackSet, moveCycles);
+      assert finished.contains(move);
+      assert stack.isEmpty();
+      assert stackSet.isEmpty();
+    }
+    return moveCycles;
+  }
+
+  private static void dfs(
+      RegisterMove move,
+      Set<RegisterMove> finished,
+      Int2ObjectMap<TreeSet<RegisterMove>> readBy,
+      Deque<RegisterMove> stack,
+      TreeSet<RegisterMove> stackSet,
+      List<RegisterMoveCycle> moveCycles) {
+    stack.addLast(move);
+    boolean addedToStack = stackSet.add(move);
+    assert addedToStack;
+    // Explore all outgoing edges. The successors of this move are the moves that read the
+    // destination registers of the current move.
+    for (RegisterMove successor : getSuccessors(move, readBy)) {
+      if (finished.contains(successor)) {
+        // Already fully explored.
+        continue;
+      }
+      if (successor.isIdentical(move)) {
+        // This move is reading/writing overlapping registers (e.g., move-wide 0, 1).
+        continue;
+      }
+      if (stackSet.contains(successor)) {
+        moveCycles.add(extractCycle(stack, successor, readBy));
+      } else {
+        dfs(successor, finished, readBy, stack, stackSet, moveCycles);
+      }
+    }
+    RegisterMove removedFromStack = stack.removeLast();
+    assert removedFromStack.isIdentical(move);
+    boolean removedFromStackSet = stackSet.remove(move);
+    assert removedFromStackSet;
+    boolean markedFinished = finished.add(move);
+    assert markedFinished;
+  }
+
+  // Returns a one-to-many map from registers to the set of moves that read that register.
+  private static Int2ObjectMap<TreeSet<RegisterMove>> createReadByGraph(
+      TreeSet<RegisterMove> moveSet) {
+    Int2ObjectMap<TreeSet<RegisterMove>> readBy = new Int2ObjectOpenHashMap<>();
+    for (RegisterMove move : moveSet) {
+      move.forEachSourceRegister(
+          register -> {
+            if (readBy.containsKey(register)) {
+              readBy.get(register).add(move);
+            } else {
+              readBy.put(register, SetUtils.newTreeSet(move));
+            }
+          });
+    }
+    return readBy;
+  }
+
+  private static Set<RegisterMove> getSuccessors(
+      RegisterMove move, Int2ObjectMap<TreeSet<RegisterMove>> readBy) {
+    TreeSet<RegisterMove> successors = readBy.get(move.dst);
+    if (move.isWide()) {
+      TreeSet<RegisterMove> additionalSuccessors = readBy.get(move.dst + 1);
+      if (successors == null) {
+        successors = additionalSuccessors;
+      } else if (additionalSuccessors != null) {
+        successors = new TreeSet<>(successors);
+        successors.addAll(additionalSuccessors);
+      }
+    }
+    return successors != null ? successors : Collections.emptySet();
+  }
+
+  private static RegisterMoveCycle extractCycle(
+      Deque<RegisterMove> stack,
+      RegisterMove cycleEntry,
+      Int2ObjectMap<TreeSet<RegisterMove>> readBy) {
+    Deque<RegisterMove> cycle = new ArrayDeque<>();
+    while (!cycleEntry.isIdentical(cycle.peekFirst())) {
+      cycle.addFirst(stack.removeLast());
+    }
+    stack.addAll(cycle);
+    TreeSet<RegisterMove> cycleSet = new TreeSet<>(cycle);
+    return new RegisterMoveCycle(cycleSet, isClosedCycle(cycleSet, readBy));
+  }
+
+  private static boolean isClosedCycle(
+      TreeSet<RegisterMove> cycle, Int2ObjectMap<TreeSet<RegisterMove>> readBy) {
+    for (RegisterMove move : cycle) {
+      for (int i = 0; i < move.type.requiredRegisters(); i++) {
+        TreeSet<RegisterMove> successors = readBy.get(move.dst + i);
+        if (successors != null) {
+          for (RegisterMove successor : successors) {
+            if (!cycle.contains(successor)) {
+              return false;
+            }
+          }
+        }
+      }
+    }
+    return true;
+  }
+}
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 242f2dc..43c2ed3 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
@@ -4,6 +4,8 @@
 package com.android.tools.r8.ir.regalloc;
 
 import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER;
+import static com.android.tools.r8.utils.IntConsumerUtils.emptyIntConsumer;
+import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -16,6 +18,8 @@
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
@@ -26,14 +30,13 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.IntConsumer;
 
 public class RegisterMoveScheduler {
   // The set of moves to schedule.
-  private final Set<RegisterMove> moveSet = new TreeSet<>();
+  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();
@@ -44,8 +47,15 @@
   // The first available temporary register.
   private final int firstTempRegister;
   private int nextTempRegister;
-  // Free registers.
+  // Free temporary registers that have been allocated by move scheduling.
   private final IntSortedSet freeRegisters = new IntRBTreeSet();
+  // Registers that can be used as temporary registers until they are assigned by a move in the
+  // move set.
+  private final IntSortedSet freeRegistersUntilAssigned = new IntRBTreeSet();
+  // 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();
 
   public RegisterMoveScheduler(
       InstructionListIterator insertAt, int firstTempRegister, Position position) {
@@ -60,6 +70,19 @@
     this(insertAt, firstTempRegister, Position.none());
   }
 
+  private void initializeFreeRegistersUntilAssigned() {
+    // All registers that are assigned by the move set but not read can be used as temporary
+    // registers until they are assigned.
+    assert activeTempRegisters.isEmpty();
+    assert freeRegistersUntilAssigned.isEmpty();
+    for (RegisterMove move : moveSet) {
+      move.forEachDestinationRegister(freeRegistersUntilAssigned::add);
+    }
+    for (RegisterMove move : moveSet) {
+      move.forEachSourceRegister(freeRegistersUntilAssigned::remove);
+    }
+  }
+
   public void addMove(RegisterMove move) {
     moveSet.add(move);
     if (move.src != NO_REGISTER) {
@@ -70,53 +93,58 @@
 
   public void schedule() {
     assert everyDestinationOnlyWrittenOnce();
+    initializeFreeRegistersUntilAssigned();
 
     // Worklist of moves that are ready to be inserted.
     Deque<RegisterMove> worklist = new ArrayDeque<>();
 
-    // Initialize worklist with the moves that do not interfere with other moves.
-    Iterator<RegisterMove> iterator = moveSet.iterator();
-    while (iterator.hasNext()) {
-      RegisterMove move = iterator.next();
-      if (!move.isBlocked(moveSet, valueMap)) {
-        worklist.addLast(move);
-        iterator.remove();
+    // If there are destination registers we can use until they are assigned, then instead of
+    // emitting these unblocked moves, we use them as temporary registers to unblock move cycles.
+    List<RegisterMoveCycle> moveCycles = RegisterMoveCycleDetector.getMoveCycles(moveSet);
+    for (RegisterMoveCycle moveCycle : moveCycles) {
+      for (RegisterMove move : moveCycle.getMoves()) {
+        assert move.isBlocked(this, moveSet, valueMap) || !moveSet.contains(move);
+        removeFromFreeRegistersUntilAssigned(move.dst, move.isWide(), emptyIntConsumer());
       }
+      // If the cycle is not closed then some moves in the cycle are blocked by other moves.
+      // TODO(b/375147902): Try to schedule these other moves before scheduling the cycle to
+      //  unblock the cycle. In JetNews 15% of move cycles are open.
+      if (moveCycle.isClosed()) {
+        assert moveSet.containsAll(moveCycle.getMoves());
+        assert worklist.isEmpty();
+        schedulePartial(moveCycle.getMoves(), worklist);
+      }
+      assert activeTempRegisters.isEmpty();
     }
 
+    // Initialize worklist with the moves that do not interfere with other moves.
+    enqueueUnblockedMoves(worklist);
+    schedulePartial(moveSet, worklist);
+    assert freeRegistersUntilAssigned.isEmpty();
+  }
+
+  private void schedulePartial(
+      TreeSet<RegisterMove> movesToSchedule, Deque<RegisterMove> worklist) {
     // Process the worklist generating moves. If the worklist becomes empty while the move set
     // still contains elements we need to use a temporary to break cycles.
-    while (!worklist.isEmpty() || !moveSet.isEmpty()) {
+    while (!worklist.isEmpty() || !movesToSchedule.isEmpty()) {
       while (!worklist.isEmpty()) {
-        RegisterMove move = worklist.removeFirst();
-        assert !move.isBlocked(moveSet, valueMap);
-        // Insert the move.
-        int generatedDest = createMove(move);
-        // Update the value map with the information that dest can be used instead of
-        // src starting now.
-        if (move.src != NO_REGISTER) {
-          valueMap.put(move.src, generatedDest);
+        while (!worklist.isEmpty()) {
+          RegisterMove move = worklist.removeFirst();
+          assert !move.isBlocked(this, moveSet, valueMap);
+          createMove(move);
         }
-        // Iterate and find the moves that were blocked because they need to write to
-        // one of the move src. That is now valid because the move src is preserved in dest.
-        iterator = moveSet.iterator();
-        while (iterator.hasNext()) {
-          RegisterMove other = iterator.next();
-          if (!other.isBlocked(moveSet, valueMap)) {
-            worklist.addLast(other);
-            iterator.remove();
-          }
-        }
+        enqueueUnblockedMoves(worklist, movesToSchedule);
       }
-      if (!moveSet.isEmpty()) {
+      if (!movesToSchedule.isEmpty()) {
         // The remaining moves are conflicting. Chose a move and unblock it by generating moves to
         // temporary registers for its destination value(s).
-        RegisterMove move = pickMoveToUnblock();
+        RegisterMove move = pickMoveToUnblock(movesToSchedule);
         createMoveDestToTemp(move);
         // TODO(b/375147902): After emitting the newly unblocked move, try to prioritize the moves
         //  that blocked it, so that we free up the temp register, rather than getting overlapping
         //  temporary registers.
-        worklist.addLast(move);
+        worklist.add(move);
       }
     }
   }
@@ -135,7 +163,7 @@
       int moveSrc = valueMap.get(move.src);
       if (moveSrc == src) {
         result.add(move);
-      } else if (move.type.isWidePrimitive() && (moveSrc + 1) == src) {
+      } else if (move.isWide() && (moveSrc + 1) == src) {
         result.add(move);
       } else if (type.isWidePrimitive() && (moveSrc - 1) == src) {
         result.add(move);
@@ -144,7 +172,7 @@
     return result;
   }
 
-  private int createMove(RegisterMove move) {
+  private void createMove(RegisterMove move) {
     Instruction instruction;
     if (move.definition != null) {
       if (move.definition.isArgument()) {
@@ -172,7 +200,43 @@
     }
     instruction.setPosition(position);
     insertAt.add(instruction);
-    return move.dst;
+
+    // Update the value map with the information that dest can be used instead of
+    // src starting now.
+    if (move.src != NO_REGISTER) {
+      valueMap.put(move.src, move.dst);
+    }
+    removeFromFreeRegistersUntilAssigned(move.dst, move.isWide(), emptyIntConsumer());
+  }
+
+  private void enqueueUnblockedMoves(Deque<RegisterMove> worklist) {
+    // Iterate and find the moves that were blocked because they need to write to one of the move
+    // src. That is now valid because the move src is preserved in dest.
+    moveSet.removeIf(
+        move -> {
+          if (move.isBlocked(this, moveSet, valueMap)) {
+            return false;
+          }
+          worklist.addLast(move);
+          return true;
+        });
+  }
+
+  private void enqueueUnblockedMoves(
+      Deque<RegisterMove> worklist, TreeSet<RegisterMove> movesToSchedule) {
+    // Iterate and find the moves that were blocked because they need to write to one of the move
+    // src. That is now valid because the move src is preserved in dest.
+    movesToSchedule.removeIf(
+        move -> {
+          if (move.isBlocked(this, moveSet, valueMap)) {
+            return false;
+          }
+          if (ObjectUtils.notIdentical(moveSet, movesToSchedule)) {
+            moveSet.remove(move);
+          }
+          worklist.addLast(move);
+          return true;
+        });
   }
 
   private void createMoveDestToTemp(RegisterMove move) {
@@ -195,15 +259,17 @@
   }
 
   private int takeFreeRegister(boolean wide) {
-    for (int freeRegister : freeRegisters) {
-      if (wide && !freeRegisters.remove(freeRegister + 1)) {
-        continue;
-      }
-      freeRegisters.remove(freeRegister);
-      return freeRegister;
+    int register = takeFreeRegisterFrom(freeRegistersUntilAssigned, wide);
+    if (register != NO_REGISTER) {
+      addActiveTempRegisters(register, wide);
+      return register;
+    }
+    register = takeFreeRegisterFrom(freeRegisters, wide);
+    if (register != NO_REGISTER) {
+      return register;
     }
     // We don't have a free register.
-    int register = allocateExtraRegister();
+    register = allocateExtraRegister();
     if (!wide) {
       return register;
     }
@@ -214,21 +280,78 @@
     return register;
   }
 
-  private void returnTemporaryRegister(int register, boolean wide) {
-    // TODO(b/375147902): If we seed the move scheduler with a set of free registers, then this
-    //  should also return non-temporary registers that are below firstTempRegister.
-    if (isTemporaryRegister(register)) {
-      freeRegisters.add(register);
-      if (wide) {
-        assert isTemporaryRegister(register + 1);
-        freeRegisters.add(register + 1);
+  private static int takeFreeRegisterFrom(IntSortedSet freeRegisters, boolean wide) {
+    for (int freeRegister : freeRegisters) {
+      if (wide && !freeRegisters.remove(freeRegister + 1)) {
+        continue;
       }
-    } else if (wide && isTemporaryRegister(register + 1)) {
+      freeRegisters.remove(freeRegister);
+      return freeRegister;
+    }
+    return NO_REGISTER;
+  }
+
+  private void addActiveTempRegisters(int register, boolean wide) {
+    boolean addedRegister = activeTempRegisters.add(register);
+    assert addedRegister;
+    if (wide) {
+      boolean addedHighRegister = activeTempRegisters.add(register + 1);
+      assert addedHighRegister;
+    }
+  }
+
+  private void returnTemporaryRegister(int register, boolean wide) {
+    if (returnActiveTempRegister(register, wide)) {
+      addFreeRegistersUntilAssigned(register, wide);
+    } else if (isExtraTemporaryRegister(register)) {
+      returnExtraTemporaryRegister(register, wide);
+    }
+  }
+
+  private boolean returnActiveTempRegister(int register, boolean wide) {
+    boolean removedRegister = activeTempRegisters.remove(register);
+    if (wide) {
+      if (removedRegister) {
+        boolean removedHighRegister = activeTempRegisters.remove(register + 1);
+        assert removedHighRegister;
+      } else {
+        assert !activeTempRegisters.contains(register + 1);
+      }
+    }
+    return removedRegister;
+  }
+
+  private void addFreeRegistersUntilAssigned(int register, boolean wide) {
+    boolean addedRegister = freeRegistersUntilAssigned.add(register);
+    assert addedRegister;
+    if (wide) {
+      boolean addedHighRegister = freeRegistersUntilAssigned.add(register + 1);
+      assert addedHighRegister;
+    }
+  }
+
+  private void returnExtraTemporaryRegister(int register, boolean wide) {
+    assert isExtraTemporaryRegister(register);
+    freeRegisters.add(register);
+    if (wide) {
+      assert isExtraTemporaryRegister(register + 1);
       freeRegisters.add(register + 1);
     }
   }
 
-  private boolean isTemporaryRegister(int register) {
+  private void removeFromFreeRegistersUntilAssigned(
+      int register, boolean wide, IntConsumer changedConsumer) {
+    if (freeRegistersUntilAssigned.remove(register)) {
+      changedConsumer.accept(register);
+    }
+    if (wide) {
+      if (freeRegistersUntilAssigned.remove(register + 1)) {
+        changedConsumer.accept(register + 1);
+      }
+    }
+  }
+
+  private boolean isExtraTemporaryRegister(int register) {
     return register >= firstTempRegister;
   }
 
@@ -244,17 +367,16 @@
     return true;
   }
 
-  private RegisterMove pickMoveToUnblock() {
-    Iterator<RegisterMove> iterator = moveSet.iterator();
-    RegisterMove move = null;
+  private RegisterMove pickMoveToUnblock(TreeSet<RegisterMove> moves) {
     // Pick a non-wide move to unblock if possible.
-    while (iterator.hasNext()) {
-      move = iterator.next();
-      if (!move.type.isWidePrimitive()) {
-        break;
-      }
+    Iterable<RegisterMove> eligible =
+        Iterables.filter(moves, move -> !move.isDestUsedAsTemporary(this));
+    RegisterMove move =
+        Iterables.find(eligible, not(RegisterMove::isWide), eligible.iterator().next());
+    moves.remove(move);
+    if (ObjectUtils.notIdentical(moves, moveSet)) {
+      moveSet.remove(move);
     }
-    iterator.remove();
     return move;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index ea46d64..7ec47cb 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -13,6 +13,7 @@
 import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
@@ -118,6 +119,12 @@
     return builder.build();
   }
 
+  public static <T extends Comparable<T>> TreeSet<T> newTreeSet(T element) {
+    TreeSet<T> result = new TreeSet<>();
+    result.add(element);
+    return result;
+  }
+
   public static <T, S> Set<T> mapIdentityHashSet(Collection<S> set, Function<S, T> fn) {
     Set<T> out = newIdentityHashSet(set.size());
     for (S element : set) {
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 ae83eee..804b482 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
@@ -388,18 +388,10 @@
     scheduler.addMove(new RegisterMove(0, 3, TypeElement.getLong()));
     scheduler.schedule();
     assertEquals(3, moves.size());
-    Move firstMove = moves.get(0).asMove();
-    Move secondMove = moves.get(1).asMove();
-    Move thirdMove = moves.get(2).asMove();
-    assertEquals(ValueType.LONG, firstMove.outType());
-    assertEquals(ValueType.LONG, secondMove.outType());
-    assertEquals(ValueType.LONG, thirdMove.outType());
-    assertEquals(42, firstMove.dest().asFixedRegisterValue().getRegister());
-    assertEquals(1, firstMove.src().asFixedRegisterValue().getRegister());
-    assertEquals(0, secondMove.dest().asFixedRegisterValue().getRegister());
-    assertEquals(3, secondMove.src().asFixedRegisterValue().getRegister());
-    assertEquals(2, thirdMove.dest().asFixedRegisterValue().getRegister());
-    assertEquals(42, thirdMove.src().asFixedRegisterValue().getRegister());
+    assertEquals("42 <- 3", toString(moves.get(0)));
+    assertEquals("2 <- 1", toString(moves.get(1)));
+    assertEquals("0 <- 42", toString(moves.get(2)));
+    assertEquals(2, scheduler.getUsedTempRegisters());
   }
 
   @Test
@@ -460,12 +452,13 @@
     scheduler.addMove(new RegisterMove(12, 19, TypeElement.getLong()));
     scheduler.schedule();
     // In order to resolve these moves, we need to use two temporary register pairs.
-    assertEquals(6, moves.size());
-    assertEquals(42, moves.get(0).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(11, moves.get(0).asMove().src().asFixedRegisterValue().getRegister());
-    assertEquals(44, moves.get(1).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(13, moves.get(1).asMove().src().asFixedRegisterValue().getRegister());
-    assertEquals(12, moves.get(2).asMove().dest().asFixedRegisterValue().getRegister());
+    assertEquals(5, moves.size());
+    assertEquals("42 <- 13", toString(moves.get(0)));
+    assertEquals("14 <- 11", toString(moves.get(1)));
+    assertEquals("10 <- 17", toString(moves.get(2)));
+    assertEquals("16 <- 42", toString(moves.get(3)));
+    assertEquals("12 <- 19", toString(moves.get(4)));
+    assertEquals(2, scheduler.getUsedTempRegisters());
   }
 
   @Test
@@ -487,19 +480,13 @@
     scheduler.addMove(new RegisterMove(23, 28, TypeElement.getLong()));
     scheduler.schedule();
     // For this example we need recursive unblocking.
-    assertEquals(6, moves.size());
-    assertEquals(42, moves.get(0).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(26, moves.get(0).asMove().src().asFixedRegisterValue().getRegister());
-    assertEquals(26, moves.get(1).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(22, moves.get(1).asMove().src().asFixedRegisterValue().getRegister());
-    assertEquals(43, moves.get(2).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(28, moves.get(2).asMove().src().asFixedRegisterValue().getRegister());
-    assertEquals(28, moves.get(3).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(42, moves.get(3).asMove().src().asFixedRegisterValue().getRegister());
-    assertEquals(29, moves.get(4).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(24, moves.get(4).asMove().src().asFixedRegisterValue().getRegister());
-    assertEquals(23, moves.get(5).asMove().dest().asFixedRegisterValue().getRegister());
-    assertEquals(43, moves.get(5).asMove().src().asFixedRegisterValue().getRegister());
+    assertEquals(5, moves.size());
+    assertEquals("42 <- 28", toString(moves.get(0)));
+    assertEquals("29 <- 24", toString(moves.get(1)));
+    assertEquals("23 <- 42", toString(moves.get(2)));
+    assertEquals("28 <- 26", toString(moves.get(3)));
+    assertEquals("26 <- 22", toString(moves.get(4)));
+    assertEquals(2, scheduler.getUsedTempRegisters());
   }
 
   @Test
@@ -522,6 +509,41 @@
     assertEquals(1, scheduler.getUsedTempRegisters());
   }
 
+  @Test
+  public void useDestinationRegisterAsTemporary() {
+    CollectMovesIterator moves = new CollectMovesIterator();
+    int temp = 42;
+    RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
+    scheduler.addMove(new RegisterMove(0, 1, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 0, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(2, 3, TypeElement.getInt()));
+    scheduler.schedule();
+    // Verify that the temp register has been reused.
+    assertEquals("2 <- 1", toString(moves.get(0)));
+    assertEquals("1 <- 0", toString(moves.get(1)));
+    assertEquals("0 <- 2", toString(moves.get(2)));
+    assertEquals("2 <- 3", toString(moves.get(3)));
+    assertEquals(0, scheduler.getUsedTempRegisters());
+  }
+
+  @Test
+  public void openMoveCycle() {
+    CollectMovesIterator moves = new CollectMovesIterator();
+    int temp = 42;
+    RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
+    scheduler.addMove(new RegisterMove(2, 0, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(0, 2, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(4, 1, TypeElement.getInt()));
+    scheduler.schedule();
+    // The cycle is blocked by the move 4 <- 1, so we should emit this move first.
+    assertEquals(4, moves.size());
+    assertEquals("4 <- 1", toString(moves.get(0)));
+    assertEquals("42 <- 2", toString(moves.get(1)));
+    assertEquals("2 <- 0", toString(moves.get(2)));
+    assertEquals("0 <- 42", toString(moves.get(3)));
+    assertEquals(2, scheduler.getUsedTempRegisters());
+  }
+
   // Debugging aid.
   private void printMoves(List<Instruction> moves) {
     System.out.println("Generated moves:");