Fix a few debug-info related issues.

Store the debug position associated with a move-exception in the move-exception instruction.
This ensures that the line information when ART breaks at catch handlers will be accurate.

Calculate live-at-block-entry locals prior to its entry instruction.

R=ager

Change-Id: Ic994ab73973457a36f7578dee214dd4d1749a2db
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index c468613..72a43f7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.MoveException;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -51,12 +52,6 @@
   private DexString emittedFile = null;
   private Int2ReferenceMap<DebugLocalInfo> emittedLocals;
 
-  // If lastMoveInstructionPc != NO_PC_INFO, then the last pc-advancing instruction was a
-  // move-exception at lastMoveInstructionPc. This is needed to maintain the art/dx specific
-  // behaviour that the move-exception pc is associated with the catch-declaration line.
-  // See debug.ExceptionTest.testStepOnCatch().
-  private int lastMoveInstructionPc = NO_PC_INFO;
-
   // Emitted events.
   private final List<DexDebugEvent> events = new ArrayList<>();
 
@@ -81,23 +76,21 @@
     }
     assert pendingLocals != null;
 
-    // If this is a position emit and exit as it always emits events.
     if (instruction.isDebugPosition()) {
       emitDebugPosition(pc, instruction.asDebugPosition());
-      return;
-    }
-
-    if (instruction.isArgument()) {
+    } else if (instruction.isMoveException()) {
+      MoveException move = instruction.asMoveException();
+      if (move.getPosition() != null) {
+        emitDebugPosition(pc, move.getPosition());
+      }
+    } else if (instruction.isArgument()) {
       startArgument(instruction.asArgument());
     } else if (instruction.isDebugLocalsChange()) {
       updateLocals(instruction.asDebugLocalsChange());
     } else if (instruction.getBlock().exit() == instruction) {
-      // If this is the end of the block clear out the pending state and exit.
+      // If this is the end of the block clear out the pending state.
       pendingLocals = null;
       pendingLocalChanges = false;
-      return;
-    } else if (instruction.isMoveException()) {
-      lastMoveInstructionPc = pc;
     } else {
       // For non-exit / pc-advancing instructions emit any pending changes.
       emitLocalChanges(pc);
@@ -191,18 +184,16 @@
   }
 
   private void emitDebugPosition(int pc, int line, DexString file) {
-    int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
-    lastMoveInstructionPc = NO_PC_INFO;
     // The position requires a pc change event and possible events for line, file and local changes.
     // Verify that we do not ever produce two subsequent positions at the same pc.
-    assert emittedPc != emitPc;
+    assert emittedPc != pc;
     if (startLine == NO_LINE_INFO) {
       assert emittedLine == NO_LINE_INFO;
       startLine = line;
       emittedLine = line;
     }
-    emitAdvancementEvents(emittedPc, emittedLine, emittedFile, emitPc, line, file, events, factory);
-    emittedPc = emitPc;
+    emitAdvancementEvents(emittedPc, emittedLine, emittedFile, pc, line, file, events, factory);
+    emittedPc = pc;
     emittedLine = line;
     emittedFile = file;
     if (localsChanged()) {
@@ -215,11 +206,9 @@
   private void emitLocalChanges(int pc) {
     // If pc advanced since the locals changed and locals indeed have changed, emit the changes.
     if (localsChanged()) {
-      int emitPc = lastMoveInstructionPc != NO_PC_INFO ? lastMoveInstructionPc : pc;
-      lastMoveInstructionPc = NO_PC_INFO;
       emitAdvancementEvents(
-          emittedPc, emittedLine, emittedFile, emitPc, emittedLine, emittedFile, events, factory);
-      emittedPc = emitPc;
+          emittedPc, emittedLine, emittedFile, pc, emittedLine, emittedFile, events, factory);
+      emittedPc = pc;
       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
       pendingLocalChanges = false;
       assert localsEqual(emittedLocals, pendingLocals);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 290a1a2..16bc790 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1120,11 +1120,13 @@
     List<BasicBlock> predecessors = this.getPredecessors();
     boolean hasMoveException = entry().isMoveException();
     MoveException move = null;
+    DebugPosition position = null;
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
+      position = move.getPosition();
       assert move.getPreviousLocalValue() == null;
-      this.getInstructions().remove(0);
+      getInstructions().remove(0);
     }
     // Create new predecessor blocks.
     List<BasicBlock> newPredecessors = new ArrayList<>();
@@ -1140,7 +1142,11 @@
         Value value = new Value(
             valueNumberGenerator.next(), MoveType.OBJECT, move.getDebugInfo());
         values.add(value);
-        newBlock.add(new MoveException(value));
+        MoveException newMove = new MoveException(value);
+        newBlock.add(newMove);
+        if (position != null) {
+          newMove.setPosition(new DebugPosition(position.line, position.file));
+        }
       }
       newBlock.add(new Goto());
       newBlock.close(null);
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 2691eed..554e71f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -9,6 +9,8 @@
 
 public class MoveException extends Instruction {
 
+  private DebugPosition position = null;
+
   public MoveException(Value dest) {
     super(dest);
   }
@@ -17,6 +19,14 @@
     return outValue;
   }
 
+  public DebugPosition getPosition() {
+    return position;
+  }
+
+  public void setPosition(DebugPosition position) {
+    this.position = position;
+  }
+
   @Override
   public void buildDex(DexBuilder builder) {
     int dest = builder.allocatedRegister(dest(), getNumber());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index ba63d80..1b9fef5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1977,6 +1977,16 @@
     if (currentDebugPosition != null) {
       DebugPosition position = currentDebugPosition;
       currentDebugPosition = null;
+      if (!currentBlock.getInstructions().isEmpty()) {
+        MoveException move = currentBlock.getInstructions().getLast().asMoveException();
+        if (move != null && move.getPosition() == null) {
+          // Set the position on the move-exception instruction.
+          // ART/DX associates the move-exception pc with the catch-declaration line.
+          // See debug.ExceptionTest.testStepOnCatch().
+          move.setPosition(position);
+          return;
+        }
+      }
       addInstruction(position);
     }
   }
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 0bd7188..1e45d52 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
@@ -28,7 +28,6 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.HashMultiset;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multiset;
 import com.google.common.collect.Multiset.Entry;
 import com.google.common.collect.Multisets;
@@ -288,7 +287,7 @@
     }
 
     for (BasicBlock block : blocks) {
-      boolean blockEntry = true;
+      block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
       ListIterator<Instruction> instructionIterator = block.listIterator();
       while (instructionIterator.hasNext()) {
         Instruction instruction = instructionIterator.next();
@@ -307,8 +306,8 @@
           }
         }
         while (nextStartingRange != null && nextStartingRange.start <= index) {
-          // If the full range is between the two debug positions ignore it.
-          if (nextStartingRange.end > index) {
+          // If the range is live at this index open it.
+          if (index < nextStartingRange.end) {
             openRanges.add(nextStartingRange);
             assert !currentLocals.containsKey(nextStartingRange.register);
             currentLocals.put(nextStartingRange.register, nextStartingRange.local);
@@ -317,10 +316,7 @@
           }
           nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
         }
-        if (blockEntry) {
-          blockEntry = false;
-          block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
-        } else if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
+        if (localsChanged && shouldEmitChangesAtInstruction(instruction)) {
           DebugLocalsChange change = createLocalsChange(ending, starting);
           if (change != null) {
             if (instruction.isDebugPosition() || instruction.isJumpInstruction()) {
@@ -368,18 +364,6 @@
         || (instruction.isGoto() && instruction.asGoto().getTarget() == code.getNormalExitBlock());
   }
 
-  private boolean verifyLocalsEqual(
-      ImmutableMap<Integer, DebugLocalInfo> a, Map<Integer, DebugLocalInfo> b) {
-    int size = 0;
-    for (Map.Entry<Integer, DebugLocalInfo> entry : b.entrySet()) {
-      if (entry.getValue() != null) {
-        assert a.get(entry.getKey()) == entry.getValue();
-        ++size;
-      }
-    }
-    return a.size() == size;
-  }
-
   private void clearState() {
     liveAtEntrySets = null;
     liveIntervals = null;