Allow using move exception register for values not live to catch

This records on each live intervals whether the given live interval is live at entry to a block that starts with a move-exception instruction.

All values that are not live at entry to a move-exception block do not need to check for overlap with move exception live intervals.

This improves build speed but also size, since this means that the move-exception split live intervals will be able to reuse the dedicated move-exception register. In other words, this fixes the issue that the compiler would almost always insert a (often) redundant move immediately after each move-exception instruction.

Change-Id: I22e0c6b040dd49c115680034adc148bc35ca72bd
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 31fe227..bb5742b 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
@@ -1139,9 +1139,8 @@
       }
       // Split their live ranges which will force another register if used.
       for (LiveIntervals intervals : moveExceptionIntervals) {
-        if (intervals.getUses().size() > 1) {
-          LiveIntervals split =
-              intervals.splitBefore(intervals.getFirstUse() + INSTRUCTION_NUMBER_DELTA);
+        if (intervals.getValue().hasAnyUsers()) {
+          LiveIntervals split = intervals.splitAfter(intervals.getValue().getDefinition());
           unhandled.add(split);
         }
       }
@@ -1482,7 +1481,7 @@
         if (hasDedicatedMoveExceptionRegister()) {
           boolean canUseMoveExceptionRegisterForLinkedIntervals =
               isDedicatedMoveExceptionRegisterInFirstLocalRegister()
-                  && !overlapsMoveExceptionInterval(start);
+                  && (!start.isLiveAtMoveExceptionEntry() || !overlapsMoveExceptionInterval(start));
           if (!canUseMoveExceptionRegisterForLinkedIntervals) {
             freeRegisters.remove(getMoveExceptionRegister());
           }
@@ -1659,7 +1658,8 @@
 
     // Check for overlap with the move exception interval.
     boolean overlapsMoveExceptionInterval =
-        hasDedicatedMoveExceptionRegister()
+        intervals.isLiveAtMoveExceptionEntry()
+            && hasDedicatedMoveExceptionRegister()
             && (register == getMoveExceptionRegister()
                 || (intervals.getType().isWide() && register + 1 == getMoveExceptionRegister()))
             && overlapsMoveExceptionInterval(intervals);
@@ -2069,10 +2069,12 @@
     // place to put a spill move (because the move exception instruction has to be the
     // first instruction in the handler block).
     if (hasDedicatedMoveExceptionRegister()) {
-      if (unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX
+      if (!mode.is4Bit()
+          && unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX
           && isDedicatedMoveExceptionRegisterInLastLocalRegister()) {
         freePositions.setBlocked(getMoveExceptionRegister());
-      } else if (overlapsMoveExceptionInterval(unhandledInterval)) {
+      } else if (unhandledInterval.isLiveAtMoveExceptionEntry()
+          && overlapsMoveExceptionInterval(unhandledInterval)) {
         int moveExceptionRegister = getMoveExceptionRegister();
         if (moveExceptionRegister <= registerConstraint) {
           freePositions.setBlocked(moveExceptionRegister);
@@ -2568,7 +2570,8 @@
 
     // Disallow reuse of the move exception register if we have reserved one.
     if (hasDedicatedMoveExceptionRegister()) {
-      if (unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX
+      if (!mode.is4Bit()
+          && unhandledInterval.getRegisterLimit() == Constants.U4BIT_MAX
           && isDedicatedMoveExceptionRegisterInLastLocalRegister()) {
         usePositions.setBlocked(getMoveExceptionRegister());
       } else if (overlapsMoveExceptionInterval(unhandledInterval)) {
@@ -3105,6 +3108,13 @@
 
   private void computeLiveRanges() {
     computeLiveRanges(appView, code, liveAtEntrySets, liveIntervals);
+    boolean hasMoveException = false;
+    for (BasicBlock block : code.blocks(block -> block.entry().isMoveException())) {
+      for (Value value : liveAtEntrySets.get(block).liveValues) {
+        value.getLiveIntervals().setIsLiveAtMoveExceptionEntry();
+      }
+      hasMoveException = true;
+    }
     // Art VMs before Android M assume that the register for the receiver never changes its value.
     // This assumption is used during verification. Allowing the receiver register to be
     // overwritten can therefore lead to verification errors. If we could be targeting one of these
@@ -3117,6 +3127,9 @@
       for (LiveAtEntrySets values : liveAtEntrySets.values()) {
         values.liveValues.add(firstArgumentValue);
       }
+      if (hasMoveException) {
+        thisIntervals.setIsLiveAtMoveExceptionEntry();
+      }
     }
   }
 
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 3a8f340..64d42b0 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
@@ -43,6 +43,7 @@
   private boolean spilled = false;
   private boolean isInvokeRangeIntervals = false;
   private boolean usedInMonitorOperations = false;
+  private boolean liveAtMoveExceptionEntry = false;
 
   // Only registers up to and including the registerLimit are allowed for this interval.
   private int registerLimit = U16BIT_MAX;
@@ -305,9 +306,19 @@
   }
 
   public void unsetIsInvokeRangeIntervals() {
+    assert isSplitParent();
     isInvokeRangeIntervals = false;
   }
 
+  public boolean isLiveAtMoveExceptionEntry() {
+    return splitParent.liveAtMoveExceptionEntry;
+  }
+
+  public void setIsLiveAtMoveExceptionEntry() {
+    assert isSplitParent();
+    liveAtMoveExceptionEntry = true;
+  }
+
   private int computeMaxNonSpilledRegister() {
     assert splitParent == this;
     assert maxNonSpilledRegister == NO_REGISTER;