Merge changes Id9b221f4,I9d00f06d

* changes:
  Support Kotlin in continuous stepping tests
  Debugging tests: fix breakpoint setup when line table is empty
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index ebfc337..84a48dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2075,11 +2075,10 @@
       return;
     }
 
-    int color = code.reserveMarkingColor();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
-      if (block.isMarked(color)) {
+      if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
         continue;
       }
       InstructionListIterator insnIterator = block.listIterator();
@@ -2096,19 +2095,13 @@
         }
 
         // Split the block.
-        BasicBlock newBlock = insnIterator.split(code, blockIterator);
-        assert !insnIterator.hasNext(); // must be pointing *after* inserted GoTo.
-        // Move block iterator back so current block is 'newBlock'.
-        blockIterator.previous();
+        {
+          BasicBlock newBlock = insnIterator.split(code, blockIterator);
+          assert !insnIterator.hasNext(); // must be pointing *after* inserted GoTo.
+          // Move block iterator back so current block is 'newBlock'.
+          blockIterator.previous();
 
-        // Unlink the next block representing all the instructions of the original
-        // block after the invocation which never returns normally. Mark all the
-        // removed blocks for deletion.
-        List<BasicBlock> removedBlocks = block.unlink(newBlock, new DominatorTree(code));
-        for (BasicBlock removedBlock : removedBlocks) {
-          if (!removedBlock.isMarked(color)) {
-            removedBlock.mark(color);
-          }
+          newBlock.unlinkSinglePredecessorSiblingsAllowed();
         }
 
         // We want to follow the invoke instruction with 'throw null', which should
@@ -2137,8 +2130,7 @@
         notReachableThrow.forceSetPosition(insn.getPosition());
       }
     }
-    code.removeMarkedBlocks(color);
-    code.returnMarkingColor(color);
+    code.removeUnreachableBlocks();
     assert code.isConsistentSSA();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index abc4730..1c0e761 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -140,6 +140,7 @@
   protected PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
 
   // List of intervals for the result of move-exception instructions.
+  // Always empty in mode ALLOW_ARGUMENT_REUSE.
   private List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
 
   // The first register used for parallel moves. After register allocation the parallel move
@@ -152,16 +153,17 @@
 
   // Whether or not the code has a move exception instruction. Used to pin the move exception
   // register.
-  private boolean hasDedicatedMoveExceptionRegister = false;
-
-  // We allocate a dedicated move exception register right after the arguments.
-  private int getMoveExceptionRegister() {
-    assert hasDedicatedMoveExceptionRegister;
-    return numberOfArgumentRegisters;
+  private boolean hasDedicatedMoveExceptionRegister() {
+    return !moveExceptionIntervals.isEmpty();
   }
 
-  private int getNextUnusedRegisterNumber() {
-    return maxRegisterNumber + 1;
+  // We allocate a dedicated move exception register right after the arguments.
+  // TODO(christofferqa): The move-exception instruction only requires its destination register to
+  // fit in 8 bits. In some situations, it might be better to use a register which is >= 16 if we
+  // end up using that many registers.
+  private int getMoveExceptionRegister() {
+    assert hasDedicatedMoveExceptionRegister();
+    return numberOfArgumentRegisters;
   }
 
   public LinearScanRegisterAllocator(IRCode code, InternalOptions options) {
@@ -740,6 +742,8 @@
       } else {
         // Treat the argument interval as spilled which will require a load to a different
         // register for all register-constrained usages.
+        inactive.add(argumentInterval);
+        // Split argument live interval at its first constrained use.
         if (argumentInterval.getUses().size() > 1) {
           LiveIntervalsUse use = argumentInterval.firstUseWithConstraint();
           if (use != null) {
@@ -751,12 +755,17 @@
             } else {
               // If there are multiple register-constrained users, split right after the definition
               // to make it more likely that arguments get in usable registers from the start.
+              // TODO(christofferqa): This is not great if there are many arguments with multiple
+              // constrained uses, since we fill up all the low registers immediately, making it
+              // likely that we will have to kick them back out before they are actually used.
               split = argumentInterval
                   .splitBefore(argumentInterval.getValue().definition.getNumber() + 1);
             }
             unhandled.add(split);
           }
         }
+        // Since we are not activating the argument live intervals, we need to free their registers.
+        freeOccupiedRegistersForIntervals(argumentInterval);
       }
       argumentValue = argumentValue.getNextConsecutive();
     }
@@ -769,40 +778,33 @@
     // When we allow argument reuse we do not allow any splitting, therefore we cannot get into
     // trouble with move exception registers. When argument reuse is disallowed we block a fixed
     // register to be used only by move exception instructions.
-    if (mode != ArgumentReuseMode.ALLOW_ARGUMENT_REUSE) {
+    if (mode == ArgumentReuseMode.DISALLOW_ARGUMENT_REUSE) {
       // Force all move exception ranges to start out with the exception in a fixed register. Split
       // their live ranges which will force another register if used.
-      int moveExceptionRegister = NO_REGISTER;
       boolean overlappingMoveExceptionIntervals = false;
       for (BasicBlock block : code.blocks) {
-        for (Instruction instruction : block.getInstructions()) {
-          if (instruction.isMoveException()) {
-            hasDedicatedMoveExceptionRegister = true;
-            Value exceptionValue = instruction.outValue();
-            LiveIntervals intervals = exceptionValue.getLiveIntervals();
-            unhandled.remove(intervals);
-            moveExceptionIntervals.add(intervals);
-            if (moveExceptionRegister == NO_REGISTER) {
-              moveExceptionRegister = getFreeConsecutiveRegisters(1);
-              assert moveExceptionRegister == getMoveExceptionRegister();
-              assert !freeRegisters.contains(moveExceptionRegister);
-            }
-            intervals.setRegister(moveExceptionRegister);
-            if (!overlappingMoveExceptionIntervals) {
-              for (LiveIntervals other : moveExceptionIntervals) {
-                overlappingMoveExceptionIntervals |= other.overlaps(intervals);
-              }
+        Instruction instruction = block.entry();
+        if (instruction.isMoveException()) {
+          LiveIntervals intervals = instruction.outValue().getLiveIntervals();
+          unhandled.remove(intervals);
+          moveExceptionIntervals.add(intervals);
+          intervals.setRegister(getMoveExceptionRegister());
+          if (!overlappingMoveExceptionIntervals) {
+            for (LiveIntervals other : moveExceptionIntervals) {
+              overlappingMoveExceptionIntervals |= other.overlaps(intervals);
             }
           }
         }
       }
+      if (hasDedicatedMoveExceptionRegister()) {
+        int moveExceptionRegister = getMoveExceptionRegister();
+        assert moveExceptionRegister == maxRegisterNumber + 1;
+        increaseCapacity(moveExceptionRegister, true);
+      }
       if (overlappingMoveExceptionIntervals) {
         for (LiveIntervals intervals : moveExceptionIntervals) {
           if (intervals.getUses().size() > 1) {
-            LiveIntervalsUse firstUse = intervals.getUses().pollFirst();
-            LiveIntervalsUse secondUse = intervals.getUses().pollFirst();
-            intervals.getUses().add(firstUse);
-            intervals.getUses().add(secondUse);
+            LiveIntervalsUse secondUse = intervals.getUses().stream().skip(1).findFirst().get();
             LiveIntervals split = intervals.splitBefore(secondUse.getPosition());
             unhandled.add(split);
           }
@@ -812,6 +814,8 @@
 
     // Go through each unhandled live interval and find a register for it.
     while (!unhandled.isEmpty()) {
+      assert invariantsHold(mode);
+
       LiveIntervals unhandledInterval = unhandled.poll();
 
       setHintForDestRegOfCheckCast(unhandledInterval);
@@ -820,9 +824,9 @@
       // If this interval value is the src of an argument move. Fix the registers for the
       // consecutive arguments now and add hints to the move sources. This looks forward
       // and propagate hints backwards to avoid many moves in connection with ranged invokes.
-      allocateArgumentIntervalsWithSrc(unhandledInterval);
+      allocateArgumentIntervalsWithSrc(unhandledInterval, mode);
       if (unhandledInterval.getRegister() != NO_REGISTER) {
-        // The value itself could be in the chain that has now gotten registers allocated.
+        // The value itself is in the chain that has now gotten registers allocated.
         continue;
       }
 
@@ -833,12 +837,12 @@
         LiveIntervals activeIntervals = activeIterator.next();
         if (start >= activeIntervals.getEnd()) {
           activeIterator.remove();
-          freeRegistersForIntervals(activeIntervals);
+          freeOccupiedRegistersForIntervals(activeIntervals);
         } else if (!activeIntervals.overlapsPosition(start)) {
           activeIterator.remove();
           assert activeIntervals.getRegister() != NO_REGISTER;
           inactive.add(activeIntervals);
-          freeRegistersForIntervals(activeIntervals);
+          freeOccupiedRegistersForIntervals(activeIntervals);
         }
       }
 
@@ -852,13 +856,13 @@
           inactiveIterator.remove();
           assert inactiveIntervals.getRegister() != NO_REGISTER;
           active.add(inactiveIntervals);
-          takeRegistersForIntervals(inactiveIntervals);
+          takeFreeRegistersForIntervals(inactiveIntervals);
         }
       }
 
       // Perform the actual allocation.
       if (unhandledInterval.isLinked() && !unhandledInterval.isArgumentInterval()) {
-        allocateLinkedIntervals(unhandledInterval);
+        allocateLinkedIntervals(unhandledInterval, false);
       } else {
         if (!allocateSingleInterval(unhandledInterval, mode)) {
           return false;
@@ -868,6 +872,76 @@
     return true;
   }
 
+  private boolean invariantsHold(ArgumentReuseMode mode) {
+    TreeSet<Integer> computedFreeRegisters = new TreeSet<>();
+    for (int register = 0; register <= maxRegisterNumber; ++register) {
+      computedFreeRegisters.add(register);
+    }
+    for (LiveIntervals activeIntervals : active) {
+      assert registersForIntervalsAreTaken(activeIntervals);
+      activeIntervals.forEachRegister(
+          register -> {
+            assert computedFreeRegisters.contains(register);
+            computedFreeRegisters.remove(register);
+          });
+    }
+    if (mode == ArgumentReuseMode.DISALLOW_ARGUMENT_REUSE) {
+      // Each time an argument interval is active, we currently require that it is present in its
+      // original, incoming argument register.
+      for (LiveIntervals activeIntervals : active) {
+        if (activeIntervals.isArgumentInterval()
+            && activeIntervals != activeIntervals.getSplitParent()) {
+          LiveIntervals parent = activeIntervals.getSplitParent();
+          if (parent.getRegister() != activeIntervals.getRegister()) {
+            activeIntervals
+                .getSplitParent()
+                .forEachRegister(
+                    register -> {
+                      assert computedFreeRegisters.contains(register);
+                      computedFreeRegisters.remove(register);
+                    });
+          }
+        }
+      }
+    }
+    if (hasDedicatedMoveExceptionRegister()) {
+      // Relax the check, since it is not currently guaranteed that the move exception register is
+      // occupied if-and-only-if there is an active live interval with the register.
+      freeRegisters.remove(getMoveExceptionRegister());
+      computedFreeRegisters.remove(getMoveExceptionRegister());
+    }
+    assert freeRegisters.equals(computedFreeRegisters);
+    return true;
+  }
+
+  private boolean freePositionsAreConsistentWithFreeRegisters(
+      RegisterPositions freePositions, int registerConstraint) {
+    int n = Math.min(maxRegisterNumber, registerConstraint);
+    for (int register = 0; register <= n; ++register) {
+      if (freePositions.get(register) > 0) {
+        // If this register is free according to freePositions, then it should also be free
+        // according to freeRegisters.
+        boolean isMoveExceptionRegister =
+            hasDedicatedMoveExceptionRegister() && register == getMoveExceptionRegister();
+        if (!isMoveExceptionRegister) {
+          assert freeRegisters.contains(register);
+        }
+      }
+    }
+    return true;
+  }
+
+  private boolean registerAssignmentNotConflictingWithArgument(LiveIntervals interval) {
+    assert interval.getRegister() != NO_REGISTER;
+    for (Value argumentValue = firstArgumentValue;
+        argumentValue != null;
+        argumentValue = argumentValue.getNextConsecutive()) {
+      assert !interval.hasConflictingRegisters(argumentValue.getLiveIntervals())
+          || !argumentValue.getLiveIntervals().anySplitOverlaps(interval);
+    }
+    return true;
+  }
+
   private void setHintForDestRegOfCheckCast(LiveIntervals unhandledInterval) {
     if (unhandledInterval.getHint() == null &&
         unhandledInterval.getValue().definition instanceof CheckCast) {
@@ -915,7 +989,7 @@
    * allocated and have been moved from unhandled to inactive. The move sources have their hints
    * updated. The rest of the register allocation state is unchanged.
    */
-  private void allocateArgumentIntervalsWithSrc(LiveIntervals srcInterval) {
+  private void allocateArgumentIntervalsWithSrc(LiveIntervals srcInterval, ArgumentReuseMode mode) {
     Value value = srcInterval.getValue();
     for (Instruction instruction : value.uniqueUsers()) {
       // If there is a move user that is an argument move, we allocate the consecutive
@@ -928,8 +1002,7 @@
         if (destIntervals.getRegister() == NO_REGISTER) {
           // Save the current register allocation state so we can restore it at the end.
           TreeSet<Integer> savedFreeRegisters = new TreeSet<>(freeRegisters);
-          int savedUnusedRegisterNumber = getNextUnusedRegisterNumber();
-          List<LiveIntervals> savedActive = new LinkedList<>(active);
+          int savedMaxRegisterNumber = maxRegisterNumber;
           List<LiveIntervals> savedInactive = new LinkedList<>(inactive);
 
           // Add all the active intervals to the inactive set. When allocating linked intervals we
@@ -944,23 +1017,35 @@
             // registers works the best for code size.
             if (active.isArgumentInterval()) {
               // Allow the ranged invoke to use argument registers if free. This improves register
-              // allocation for brigde methods that forwards all of their arguments after check-cast
+              // allocation for bridge methods that forwards all of their arguments after check-cast
               // checks on their types.
-              freeRegistersForIntervals(active);
+              freeOccupiedRegistersForIntervals(active);
             }
             inactive.add(active);
           }
 
           // Allocate the argument intervals.
           unhandled.remove(destIntervals);
-          allocateLinkedIntervals(destIntervals);
+          boolean excludeUnhandledOverlappingArgumentIntervals = false;
+          if (mode == ArgumentReuseMode.DISALLOW_ARGUMENT_REUSE) {
+            // Since we are going to do a look-ahead, there may be argument live interval splits,
+            // which are currently unhandled, but would be inactive at the invoke-range instruction.
+            // Thus, the implementation of allocateLinkedIntervals needs to exclude the argument
+            // registers for which there exists a split that overlaps with one of the inputs to the
+            // invoke-range instruction. We handle this situation by setting the following flag.
+            excludeUnhandledOverlappingArgumentIntervals = true;
+          }
+          unhandled.add(srcInterval);
+          allocateLinkedIntervals(destIntervals, excludeUnhandledOverlappingArgumentIntervals);
+          active.remove(destIntervals);
+          unhandled.remove(srcInterval);
           // Restore the register allocation state.
           freeRegisters = savedFreeRegisters;
           // In case maxRegisterNumber has changed, update freeRegisters.
-          for (int i = savedUnusedRegisterNumber, j = getNextUnusedRegisterNumber(); i < j; i++) {
+          for (int i = savedMaxRegisterNumber + 1; i <= maxRegisterNumber; i++) {
             freeRegisters.add(i);
           }
-          active = savedActive;
+
           inactive = savedInactive;
           // Move all the argument intervals to the inactive set.
           LiveIntervals current = destIntervals.getStartOfConsecutive();
@@ -976,35 +1061,43 @@
     }
   }
 
-  private void allocateLinkedIntervals(LiveIntervals unhandledInterval) {
+  private void allocateLinkedIntervals(
+      LiveIntervals unhandledInterval, boolean excludeUnhandledOverlappingArgumentIntervals) {
+    LiveIntervals start = unhandledInterval.getStartOfConsecutive();
+
     // Exclude the registers that overlap the start of one of the live ranges we are
     // going to assign registers to now.
-    LiveIntervals current = unhandledInterval.getStartOfConsecutive();
-    Set<Integer> excludedRegisters = new HashSet<>();
-    while (current != null) {
+    IntSet excludedRegisters = new IntArraySet();
+    for (LiveIntervals current = start; current != null; current = current.getNextConsecutive()) {
       for (LiveIntervals inactiveIntervals : inactive) {
         if (inactiveIntervals.overlaps(current)) {
           excludeRegistersForInterval(inactiveIntervals, excludedRegisters);
         }
       }
-      current = current.getNextConsecutive();
     }
-    current = unhandledInterval.getStartOfConsecutive();
+    if (excludeUnhandledOverlappingArgumentIntervals && firstArgumentValue != null) {
+      // Exclude the argument registers for which there exists a split that overlaps with one of
+      // the inputs to the invoke-range instruction.
+      for (LiveIntervals intervals = firstArgumentValue.getLiveIntervals();
+          intervals != null;
+          intervals = intervals.getNextConsecutive()) {
+        if (liveIntervalsHasUnhandledSplitOverlappingAnyOf(intervals, start)) {
+          excludeRegistersForInterval(intervals, excludedRegisters);
+        }
+      }
+    }
     // Exclude move exception register if the first interval overlaps a move exception interval.
-    if (overlapsMoveExceptionInterval(current) &&
-        freeRegisters.remove(getMoveExceptionRegister())) {
+    // It is not necessary to check the remaining consecutive intervals, since we always use
+    // register 0 (after remapping) for the argument register.
+    if (overlapsMoveExceptionInterval(start) && freeRegisters.remove(getMoveExceptionRegister())) {
       excludedRegisters.add(getMoveExceptionRegister());
     }
     // Select registers.
-    int numberOfRegister = current.numberOfConsecutiveRegisters();
-    int firstRegister = getFreeConsecutiveRegisters(numberOfRegister);
-    for (int i = 0; i < numberOfRegister; i++) {
-      assert current != null;
-      current.setRegister(firstRegister + i);
-      if (current.getType().isWide()) {
-        assert i < numberOfRegister;
-        i++;
-      }
+    int numberOfRegisters = start.numberOfConsecutiveRegisters();
+    int nextRegister = getFreeConsecutiveRegisters(numberOfRegisters);
+    for (LiveIntervals current = start; current != null; current = current.getNextConsecutive()) {
+      current.setRegister(nextRegister);
+      assert registerAssignmentNotConflictingWithArgument(current);
       // Propagate hints to the move sources.
       Value value = current.getValue();
       if (!value.isPhi() && value.definition.isMove()) {
@@ -1017,32 +1110,43 @@
         // intervals in the chain have been assigned registers but their start has not yet been
         // reached. Therefore, they belong in the inactive set.
         unhandled.remove(current);
-        assert current.getRegister() != NO_REGISTER;
         inactive.add(current);
-        freeRegistersForIntervals(current);
       }
-      current = current.getNextConsecutive();
+      nextRegister += current.requiredRegisters();
     }
-    assert current == null;
+
     assert unhandledInterval.getRegister() != NO_REGISTER;
+    takeFreeRegistersForIntervals(unhandledInterval);
     active.add(unhandledInterval);
     // Include the registers for inactive ranges that we had to exclude for this allocation.
     freeRegisters.addAll(excludedRegisters);
   }
 
-  // Update the information about used registers when |register| has been selected for use.
-  private void updateRegisterState(int register, boolean needsRegisterPair) {
-    int maxRegister = register + (needsRegisterPair ? 1 : 0);
-    maxRegisterNumber = Math.max(maxRegisterNumber, maxRegister);
+  // Returns true if intervals has an unhandled split, which overlaps with chain or any of its
+  // consecutives.
+  private boolean liveIntervalsHasUnhandledSplitOverlappingAnyOf(
+      LiveIntervals intervals, LiveIntervals chain) {
+    assert intervals == intervals.getSplitParent();
+    assert chain == chain.getStartOfConsecutive();
+    for (LiveIntervals split : intervals.getSplitChildren()) {
+      if (unhandled.contains(split)) {
+        for (LiveIntervals current = chain;
+            current != null;
+            current = current.getNextConsecutive()) {
+          if (split.overlaps(current)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
   }
 
   private int getSpillRegister(LiveIntervals intervals) {
-    int registerNumber = getNextUnusedRegisterNumber();
-    maxRegisterNumber = registerNumber;
-    if (intervals.getType().isWide()) {
-      maxRegisterNumber++;
-    }
-    return registerNumber;
+    int register = maxRegisterNumber + 1;
+    increaseCapacity(maxRegisterNumber + intervals.requiredRegisters());
+    assert registersAreFree(register, intervals.getType().isWide());
+    return register;
   }
 
   private int toInstructionPosition(int position) {
@@ -1232,7 +1336,7 @@
   //   invoke method r4
   //   return
   private boolean overlapsMoveExceptionInterval(LiveIntervals intervals) {
-    if (!hasDedicatedMoveExceptionRegister) {
+    if (!hasDedicatedMoveExceptionRegister()) {
       return false;
     }
     for (LiveIntervals moveExceptionInterval : moveExceptionIntervals) {
@@ -1254,7 +1358,7 @@
     // avoid move generation for the argument.
     if (registerConstraint == Constants.U16BIT_MAX && unhandledInterval.isArgumentInterval()) {
       int argumentRegister = unhandledInterval.getSplitParent().getRegister();
-      assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, argumentRegister);
+      assignFreeRegisterToUnhandledInterval(unhandledInterval, argumentRegister);
       return true;
     }
 
@@ -1333,6 +1437,8 @@
       }
     }
 
+    assert freePositionsAreConsistentWithFreeRegisters(freePositions, registerConstraint);
+
     // Attempt to use register hints.
     if (useRegisterHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair)) {
       return true;
@@ -1376,26 +1482,35 @@
           register = getSpillRegister(unhandledInterval);
         }
         LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
-        assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, register);
+        assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
         unhandled.add(split);
       } else {
         allocateBlockedRegister(unhandledInterval);
       }
-    } else if (largestFreePosition >= unhandledInterval.getEnd()) {
-      // Free for the entire interval. Allocate the register.
-      assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, candidate);
     } else {
-      if (mode == ArgumentReuseMode.ALLOW_ARGUMENT_REUSE) {
-        // No splitting is allowed when we allow argument reuse. Bailout and start over with
-        // argument reuse disallowed.
-        return false;
+      // We will use the candidate register(s) for unhandledInterval, and therefore potentially
+      // need to adjust maxRegisterNumber.
+      int candidateEnd = candidate + unhandledInterval.requiredRegisters() - 1;
+      if (candidateEnd > maxRegisterNumber) {
+        increaseCapacity(candidateEnd);
       }
-      // The candidate is free for the beginning of an interval. We split the interval
-      // and use the register for as long as we can.
-      LiveIntervals split = unhandledInterval.splitBefore(largestFreePosition);
-      assert split != unhandledInterval;
-      assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, candidate);
-      unhandled.add(split);
+
+      if (largestFreePosition >= unhandledInterval.getEnd()) {
+        // Free for the entire interval. Allocate the register.
+        assignFreeRegisterToUnhandledInterval(unhandledInterval, candidate);
+      } else {
+        if (mode == ArgumentReuseMode.ALLOW_ARGUMENT_REUSE) {
+          // No splitting is allowed when we allow argument reuse. Bailout and start over with
+          // argument reuse disallowed.
+          return false;
+        }
+        // The candidate is free for the beginning of an interval. We split the interval
+        // and use the register for as long as we can.
+        LiveIntervals split = unhandledInterval.splitBefore(largestFreePosition);
+        assert split != unhandledInterval;
+        assignFreeRegisterToUnhandledInterval(unhandledInterval, candidate);
+        unhandled.add(split);
+      }
     }
     return true;
   }
@@ -1473,7 +1588,7 @@
             isArrayGetArrayRegister(unhandledInterval, register)) {
           return false;
         }
-        assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, register);
+        assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
         return true;
       }
     }
@@ -1481,6 +1596,7 @@
   }
 
   private void assignRegister(LiveIntervals intervals, int register) {
+    assert register + intervals.requiredRegisters() - 1 <= maxRegisterNumber;
     intervals.setRegister(register);
     updateRegisterHints(intervals);
   }
@@ -1520,11 +1636,10 @@
     }
   }
 
-  private void assignRegisterToUnhandledInterval(
-      LiveIntervals unhandledInterval, boolean needsRegisterPair, int register) {
+  private void assignFreeRegisterToUnhandledInterval(
+      LiveIntervals unhandledInterval, int register) {
     assignRegister(unhandledInterval, register);
-    takeRegistersForIntervals(unhandledInterval);
-    updateRegisterState(register, needsRegisterPair);
+    takeFreeRegistersForIntervals(unhandledInterval);
     active.add(unhandledInterval);
   }
 
@@ -1665,7 +1780,7 @@
         blockedPositions);
 
     // Get the register (pair) that has the highest use position.
-    boolean needsRegisterPair = unhandledInterval.requiredRegisters() == 2;
+    boolean needsRegisterPair = unhandledInterval.getType().isWide();
 
     // First look for a candidate that can be rematerialized.
     int candidate = getLargestValidCandidate(unhandledInterval, registerConstraint,
@@ -1705,17 +1820,26 @@
       LiveIntervals split = unhandledInterval.splitBefore(splitPosition);
       assert split != unhandledInterval;
       int registerNumber = getSpillRegister(unhandledInterval);
-      assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, registerNumber);
+      assignFreeRegisterToUnhandledInterval(unhandledInterval, registerNumber);
       unhandledInterval.setSpilled(true);
       unhandled.add(split);
-    } else if (blockedPosition > unhandledInterval.getEnd()) {
-      // Spilling can make a register available for the entire interval.
-      assignRegisterAndSpill(unhandledInterval, needsRegisterPair, candidate);
     } else {
-      // Spilling only makes a register available for the first part of current.
-      LiveIntervals splitChild = unhandledInterval.splitBefore(blockedPosition);
-      unhandled.add(splitChild);
-      assignRegisterAndSpill(unhandledInterval, needsRegisterPair, candidate);
+      // We will use the candidate register(s) for unhandledInterval, and therefore potentially
+      // need to adjust maxRegisterNumber.
+      int candidateEnd = candidate + unhandledInterval.requiredRegisters() - 1;
+      if (candidateEnd > maxRegisterNumber) {
+        increaseCapacity(candidateEnd);
+      }
+
+      if (blockedPosition > unhandledInterval.getEnd()) {
+        // Spilling can make a register available for the entire interval.
+        assignRegisterAndSpill(unhandledInterval, candidate, needsRegisterPair);
+      } else {
+        // Spilling only makes a register available for the first part of current.
+        LiveIntervals splitChild = unhandledInterval.splitBefore(blockedPosition);
+        unhandled.add(splitChild);
+        assignRegisterAndSpill(unhandledInterval, candidate, needsRegisterPair);
+      }
     }
   }
 
@@ -1729,29 +1853,25 @@
   }
 
   private void assignRegisterAndSpill(
-      LiveIntervals unhandledInterval,
-      boolean needsRegisterPair,
-      int candidate) {
-    assignRegister(unhandledInterval, candidate);
-    updateRegisterState(candidate, needsRegisterPair);
+      LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
     // Split and spill intersecting active intervals for this register.
-    spillOverlappingActiveIntervals(unhandledInterval, needsRegisterPair, candidate);
-    takeRegistersForIntervals(unhandledInterval);
+    spillOverlappingActiveIntervals(unhandledInterval, candidate, candidateIsWide);
+    // Now that that active intervals have been spilled, we are free to take the candidate.
+    assignRegister(unhandledInterval, candidate);
+    takeFreeRegistersForIntervals(unhandledInterval);
     active.add(unhandledInterval);
     // Split all overlapping inactive intervals for this register. They need to have a new
     // register assigned at the next use.
-    splitOverlappingInactiveIntervals(unhandledInterval, needsRegisterPair, candidate);
+    splitOverlappingInactiveIntervals(unhandledInterval, candidate, candidateIsWide);
   }
 
   protected void splitOverlappingInactiveIntervals(
-      LiveIntervals unhandledInterval,
-      boolean needsRegisterPair,
-      int candidate) {
+      LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
     List<LiveIntervals> newInactive = new ArrayList<>();
     Iterator<LiveIntervals> inactiveIterator = inactive.iterator();
     while (inactiveIterator.hasNext()) {
       LiveIntervals intervals = inactiveIterator.next();
-      if (intervals.usesRegister(candidate, needsRegisterPair)
+      if (intervals.usesRegister(candidate, candidateIsWide)
           && intervals.overlaps(unhandledInterval)) {
         if (intervals.isLinked() && !intervals.isArgumentInterval()) {
           // If the inactive register is linked but not an argument, it needs to get the
@@ -1783,21 +1903,26 @@
   }
 
   private void spillOverlappingActiveIntervals(
-      LiveIntervals unhandledInterval,
-      boolean needsRegisterPair,
-      int candidate) {
+      LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
+    assert unhandledInterval.getRegister() == NO_REGISTER;
+    assert atLeastOneOfRegistersAreTaken(candidate, candidateIsWide);
+    // Spill overlapping active intervals.
     List<LiveIntervals> newActive = new ArrayList<>();
     Iterator<LiveIntervals> activeIterator = active.iterator();
     while (activeIterator.hasNext()) {
       LiveIntervals intervals = activeIterator.next();
-      if (intervals.usesRegister(candidate, needsRegisterPair)) {
+      assert registersForIntervalsAreTaken(intervals);
+      if (intervals.usesRegister(candidate, candidateIsWide)) {
         activeIterator.remove();
-        freeRegistersForIntervals(intervals);
-        LiveIntervals splitChild = intervals.splitBefore(unhandledInterval.getStart());
         int registerNumber = getSpillRegister(intervals);
+        // Important not to free the registers for intervals before finding a spill register,
+        // because we might otherwise end up spilling to the current registers of intervals,
+        // depending on getSpillRegister.
+        freeOccupiedRegistersForIntervals(intervals);
+        LiveIntervals splitChild = intervals.splitBefore(unhandledInterval.getStart());
         assignRegister(splitChild, registerNumber);
         splitChild.setSpilled(true);
-        takeRegistersForIntervals(splitChild);
+        takeFreeRegistersForIntervals(splitChild);
         assert splitChild.getRegister() != NO_REGISTER;
         assert intervals.getRegister() != NO_REGISTER;
         newActive.add(splitChild);
@@ -1827,6 +1952,7 @@
       }
     }
     active.addAll(newActive);
+    assert registersAreFree(candidate, candidateIsWide);
   }
 
   private void splitRangesForSpilledArgument(LiveIntervals spilled) {
@@ -1847,7 +1973,9 @@
     assert spilled.isSpilled();
     assert !spilled.getValue().isConstNumber();
     assert !spilled.isLinked() || spilled.isArgumentInterval();
-    if (spilled.isArgumentInterval()) {
+    boolean isSpillingToArgumentRegister =
+        (spilled.isArgumentInterval() || registerNumber < numberOfArgumentRegisters);
+    if (isSpillingToArgumentRegister) {
       registerNumber = Constants.U16BIT_MAX;
     }
     LiveIntervalsUse firstUseWithLowerLimit = null;
@@ -2325,22 +2453,33 @@
   private void pinArgumentRegisters() {
     // Special handling for arguments. Pin their register.
     if (firstArgumentValue != null) {
+      increaseCapacity(numberOfArgumentRegisters - 1, true);
       int register = 0;
-      Value current = firstArgumentValue;
-      while (current != null) {
+      for (Value current = firstArgumentValue;
+          current != null;
+          current = current.getNextConsecutive()) {
         LiveIntervals argumentLiveInterval = current.getLiveIntervals();
-        // Pin the argument register. We use getFreeConsecutiveRegisters to make sure that we update
-        // the max register number.
-        register = getFreeConsecutiveRegisters(argumentLiveInterval.requiredRegisters());
         assignRegister(argumentLiveInterval, register);
-        current = current.getNextConsecutive();
+        register += current.requiredRegisters();
       }
-      // If the last argument is wide, then its register will be numberOfArgumentRegisters - 2.
-      assert register == numberOfArgumentRegisters - 1 || register == numberOfArgumentRegisters - 2;
     }
   }
 
+  private void increaseCapacity(int newMaxRegisterNumber) {
+    increaseCapacity(newMaxRegisterNumber, false);
+  }
+
+  private void increaseCapacity(int newMaxRegisterNumber, boolean takeRegisters) {
+    if (!takeRegisters) {
+      for (int register = maxRegisterNumber + 1; register <= newMaxRegisterNumber; ++register) {
+        freeRegisters.add(register);
+      }
+    }
+    maxRegisterNumber = newMaxRegisterNumber;
+  }
+
   private int getFreeConsecutiveRegisters(int numberOfRegisters) {
+    int oldMaxRegisterNumber = maxRegisterNumber;
     Iterator<Integer> freeRegistersIterator = freeRegisters.iterator();
     int first = getNextFreeRegister(freeRegistersIterator);
     int current = first;
@@ -2357,8 +2496,9 @@
         current++;
       }
     }
-    for (int register = first; register < first + numberOfRegisters; ++register) {
-      freeRegisters.remove(register);
+    for (int register = oldMaxRegisterNumber + 1; register <= maxRegisterNumber; ++register) {
+      boolean wasAdded = freeRegisters.add(register);
+      assert wasAdded;
     }
     // Either all the consecutive registers are from the argument registers, or all are from the
     // non-argument registers.
@@ -2373,35 +2513,87 @@
     if (freeRegistersIterator.hasNext()) {
       return freeRegistersIterator.next();
     }
-    maxRegisterNumber = getNextUnusedRegisterNumber();
-    return maxRegisterNumber;
+    return ++maxRegisterNumber;
   }
 
-  private void excludeRegistersForInterval(LiveIntervals intervals, Set<Integer> excluded) {
+  private void excludeRegistersForInterval(LiveIntervals intervals, IntSet excluded) {
     int register = intervals.getRegister();
+    assert register != NO_REGISTER;
+
     for (int i = 0; i < intervals.requiredRegisters(); i++) {
       if (freeRegisters.remove(register + i)) {
         excluded.add(register + i);
       }
     }
+
+    if (intervals.isArgumentInterval() && intervals != intervals.getSplitParent()) {
+      LiveIntervals parent = intervals.getSplitParent();
+      if (parent.getRegister() != register) {
+        excludeRegistersForInterval(parent, excluded);
+      }
+    }
   }
 
-  private void freeRegistersForIntervals(LiveIntervals intervals) {
+  private void freeOccupiedRegistersForIntervals(LiveIntervals intervals) {
+    assert registersForIntervalsAreTaken(intervals);
     int register = intervals.getRegister();
+    assert register + intervals.requiredRegisters() - 1 <= maxRegisterNumber;
     freeRegisters.add(register);
     if (intervals.getType().isWide()) {
       freeRegisters.add(register + 1);
     }
+
+    if (intervals.isArgumentInterval() && intervals != intervals.getSplitParent()) {
+      LiveIntervals parent = intervals.getSplitParent();
+      if (parent.getRegister() != intervals.getRegister()) {
+        freeOccupiedRegistersForIntervals(intervals.getSplitParent());
+      }
+    }
   }
 
-  private void takeRegistersForIntervals(LiveIntervals intervals) {
-    int register = intervals.getRegister();
+  private void takeFreeRegisters(int register, boolean isWide) {
+    assert registersAreFree(register, isWide);
     freeRegisters.remove(register);
-    if (intervals.getType().isWide()) {
+    if (isWide) {
       freeRegisters.remove(register + 1);
     }
   }
 
+  private void takeFreeRegistersForIntervals(LiveIntervals intervals) {
+    takeFreeRegisters(intervals.getRegister(), intervals.getType().isWide());
+
+    if (intervals.isArgumentInterval() && intervals != intervals.getSplitParent()) {
+      LiveIntervals parent = intervals.getSplitParent();
+      if (parent.getRegister() != intervals.getRegister()) {
+        takeFreeRegistersForIntervals(parent);
+      }
+    }
+  }
+
+  private boolean registerIsFree(int register) {
+    return freeRegisters.contains(register)
+        || (hasDedicatedMoveExceptionRegister() && register == getMoveExceptionRegister());
+  }
+
+  // Note: treats a register as free if it is in the set of free registers, or it is the dedicated
+  // move exception register.
+  private boolean registersAreFree(int register, boolean isWide) {
+    return registerIsFree(register) && (!isWide || registerIsFree(register + 1));
+  }
+
+  private boolean registersAreTaken(int register, boolean isWide) {
+    return !freeRegisters.contains(register) && (!isWide || !freeRegisters.contains(register + 1));
+  }
+
+  private boolean registersForIntervalsAreTaken(LiveIntervals intervals) {
+    assert intervals.getRegister() != NO_REGISTER;
+    return registersAreTaken(intervals.getRegister(), intervals.getType().isWide());
+  }
+
+  private boolean atLeastOneOfRegistersAreTaken(int register, boolean isWide) {
+    return !freeRegisters.contains(register) || (isWide && !freeRegisters.contains(register + 1));
+  }
+
   private boolean noLinkedValues() {
     for (BasicBlock block : code.blocks) {
       for (Phi phi : block.getPhis()) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index df5df48..7e398e6 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -15,6 +15,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.TreeSet;
+import java.util.function.IntConsumer;
 
 public class LiveIntervals implements Comparable<LiveIntervals> {
 
@@ -383,6 +384,14 @@
     return null;
   }
 
+  public void forEachRegister(IntConsumer consumer) {
+    assert register != NO_REGISTER;
+    consumer.accept(register);
+    if (getType().isWide()) {
+      consumer.accept(register + 1);
+    }
+  }
+
   public LiveIntervals splitBefore(int start) {
     if (toInstructionPosition(start) == toInstructionPosition(getStart())) {
       assert uses.size() == 0 || getFirstUse() != start;
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index f65e204..e8d21f2 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1118,21 +1118,27 @@
                       }));
         }
 
+        private String convertCurrentLocationToString() {
+          return getSourceFile() + ":" + getLineNumber()
+              + " (pc 0x" + Long.toHexString(location.index) + ")";
+        }
+
         private void failNoLocal(String localName) {
-          Assert.fail(
-              "line " + getLineNumber() + ": Expected local '" + localName + "' not present");
+          String locationString = convertCurrentLocationToString();
+          Assert.fail("expected local '" + localName + "' is not present at " + locationString);
         }
 
         @Override
         public void checkNoLocal(String localName) {
-          Optional<Variable> localVar = JUnit3Wrapper
-              .getVariableAt(mirror, getLocation(), localName);
-          Assert.assertFalse("Unexpected local: " + localName, localVar.isPresent());
+          Optional<Variable> localVar = getVariableAt(mirror, getLocation(), localName);
+          if (localVar.isPresent()) {
+            String locationString = convertCurrentLocationToString();
+            Assert.fail("unexpected local '" + localName + "' is present at " + locationString);
+          }
         }
 
         public void checkLocal(String localName) {
-          Optional<Variable> localVar = JUnit3Wrapper
-              .getVariableAt(mirror, getLocation(), localName);
+          Optional<Variable> localVar = getVariableAt(mirror, getLocation(), localName);
           if (!localVar.isPresent()) {
             failNoLocal(localName);
           }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index e76177c..9866b34 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -29,7 +29,7 @@
     }
 
     public void splitOverlappingInactiveIntervals(LiveIntervals intervals, int register) {
-      splitOverlappingInactiveIntervals(intervals, false, register);
+      splitOverlappingInactiveIntervals(intervals, register, false);
     }
 
     public PriorityQueue<LiveIntervals> getUnhandled() {