Mark debug usage type directly on users.

R=ager

Change-Id: I4b025f2b5a5536f3802da01d902749c78d42d3d0
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index f5bc204..40a4776 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -140,29 +140,13 @@
       assert newInstruction.outValue() != null;
       current.outValue().replaceUsers(newInstruction.outValue());
     }
-    for (Value value : current.getDebugValues()) {
-      replaceInstructionInList(current, newInstruction, value.getDebugLocalStarts());
-      replaceInstructionInList(current, newInstruction, value.getDebugLocalEnds());
-      value.removeDebugUser(current);
-      newInstruction.addDebugValue(value);
-    }
+    current.moveDebugValues(newInstruction);
     newInstruction.setBlock(block);
     listIterator.remove();
     listIterator.add(newInstruction);
     current.clearBlock();
   }
 
-  private static void replaceInstructionInList(
-      Instruction instruction,
-      Instruction newInstruction,
-      List<Instruction> instructions) {
-    for (int i = 0; i < instructions.size(); i++) {
-      if (instructions.get(i) == instruction) {
-        instructions.set(i, newInstruction);
-      }
-    }
-  }
-
   public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) {
     List<BasicBlock> blocks = code.blocks;
     assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index e99dbf1..a8004a1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -74,8 +74,9 @@
     if (debugValues == null) {
       debugValues = new HashSet<>();
     }
-    debugValues.add(value);
-    value.addDebugUser(this);
+    if (debugValues.add(value)) {
+      value.addDebugUser(this);
+    }
   }
 
   public static void clearUserInfo(Instruction instruction) {
@@ -115,6 +116,16 @@
     }
   }
 
+  public void moveDebugValues(Instruction target) {
+    if (debugValues == null) {
+      return;
+    }
+    for (Value value : debugValues) {
+      value.replaceDebugUser(this, target);
+    }
+    debugValues.clear();
+  }
+
   /**
    * Returns the basic block containing this instruction.
    */
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index dfa8548..7412dd7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.utils.InternalOptions;
@@ -11,9 +12,12 @@
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 public class Value {
@@ -21,17 +25,49 @@
   // Lazily allocated internal data for the debug information of locals.
   // This is wrapped in a class to avoid multiple pointers in the value structure.
   private static class DebugData {
+
     final DebugLocalInfo local;
-    Set<Instruction> users = new HashSet<>();
+    Map<Instruction, DebugUse> users = new HashMap<>();
     Set<Phi> phiUsers = new HashSet<>();
-    List<Instruction> localStarts = new ArrayList<>();
-    List<Instruction> localEnds = new ArrayList<>();
 
     DebugData(DebugLocalInfo local) {
       this.local = local;
     }
   }
 
+  // A debug-value user represents a point where the value is live, ends or starts.
+  // If a point is marked as both ending and starting then it is simply live, but we maintain
+  // the marker so as not to unintentionally end it if marked again.
+  private enum DebugUse {
+    LIVE, START, END, LIVE_FINAL;
+
+    DebugUse start() {
+      switch (this) {
+        case LIVE:
+        case START:
+          return START;
+        case END:
+        case LIVE_FINAL:
+          return LIVE_FINAL;
+        default:
+          throw new Unreachable();
+      }
+    }
+
+    DebugUse end() {
+      switch (this) {
+        case LIVE:
+        case END:
+          return END;
+        case START:
+        case LIVE_FINAL:
+          return LIVE_FINAL;
+        default:
+          throw new Unreachable();
+      }
+    }
+  }
+
   public static final Value UNDEFINED = new Value(-1, MoveType.OBJECT, null);
 
   protected final int number;
@@ -79,21 +115,49 @@
   }
 
   public List<Instruction> getDebugLocalStarts() {
-    return debugData.localStarts;
+    if (debugData == null) {
+      return Collections.emptyList();
+    }
+    List<Instruction> starts = new ArrayList<>(debugData.users.size());
+    for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
+      if (entry.getValue() == DebugUse.START) {
+        starts.add(entry.getKey());
+      }
+    }
+    return starts;
   }
 
   public List<Instruction> getDebugLocalEnds() {
-    return debugData.localEnds;
+    if (debugData == null) {
+      return Collections.emptyList();
+    }
+    List<Instruction> ends = new ArrayList<>(debugData.users.size());
+    for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
+      if (entry.getValue() == DebugUse.END) {
+        ends.add(entry.getKey());
+      }
+    }
+    return ends;
   }
 
   public void addDebugLocalStart(Instruction start) {
     assert start != null;
-    debugData.localStarts.add(start);
+    debugData.users.put(start, markStart(debugData.users.get(start)));
+  }
+
+  private DebugUse markStart(DebugUse use) {
+    assert use != null;
+    return use == null ? DebugUse.START : use.start();
   }
 
   public void addDebugLocalEnd(Instruction end) {
     assert end != null;
-    debugData.localEnds.add(end);
+    debugData.users.put(end, markEnd(debugData.users.get(end)));
+  }
+
+  private DebugUse markEnd(DebugUse use) {
+    assert use != null;
+    return use == null ? DebugUse.END : use.end();
   }
 
   public void linkTo(Value other) {
@@ -152,7 +216,7 @@
   }
 
   public Set<Instruction> debugUsers() {
-    return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.users.keySet());
   }
 
   public Set<Phi> debugPhiUsers() {
@@ -244,7 +308,7 @@
     if (isUninitializedLocal()) {
       return;
     }
-    debugData.users.add(user);
+    debugData.users.putIfAbsent(user, DebugUse.LIVE);
   }
 
   public void addDebugPhiUser(Phi user) {
@@ -263,11 +327,19 @@
   }
 
   public void removeDebugUser(Instruction user) {
-    debugData.users.remove(user);
+    if (debugData != null && debugData.users != null) {
+      debugData.users.remove(user);
+      return;
+    }
+    assert false;
   }
 
   public void removeDebugPhiUser(Phi user) {
-    debugData.phiUsers.remove(user);
+    if (debugData != null && debugData.phiUsers != null) {
+      debugData.phiUsers.remove(user);
+      return;
+    }
+    assert false;
   }
 
   public boolean hasUsersInfo() {
@@ -355,6 +427,14 @@
     }
   }
 
+  public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
+    DebugUse use = debugData.users.remove(oldUser);
+    if (use != null) {
+      newUser.addDebugValue(this);
+      debugData.users.put(newUser, use);
+    }
+  }
+
   public void setLiveIntervals(LiveIntervals intervals) {
     assert liveIntervals == null;
     liveIntervals = intervals;
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 7f7b726..4f462cf 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
@@ -1792,12 +1792,7 @@
         }
         Goto gotoExit = new Goto();
         gotoExit.setBlock(block);
-        if (options.debug) {
-          for (Value value : ret.getDebugValues()) {
-            gotoExit.addDebugValue(value);
-            value.removeDebugUser(ret);
-          }
-        }
+        ret.moveDebugValues(gotoExit);
         instructions.set(instructions.size() - 1, gotoExit);
         block.link(normalExitBlock);
         gotoExit.setTarget(normalExitBlock);
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 6cd453b..f4c51ce 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
@@ -197,7 +197,6 @@
       }
     }
 
-    clearUserInfo();
     assert code.isConsistentGraph();
     if (Log.ENABLED) {
       Log.debug(this.getClass(), toString());
@@ -206,6 +205,7 @@
     if (debug) {
       computeDebugInfo(blocks);
     }
+    clearUserInfo();
     clearState();
   }