Mirror def-use setup for normal values and debug-local values.

R=ager, shertz

Change-Id: Ia00c4004c79df5e13517ae4b262801ba03138584
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index 8a001ae..49adcb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -59,26 +59,26 @@
       int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
       int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
       int result = foldIntegers(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else if (type == NumericType.LONG) {
       long left = leftValue().getConstInstruction().asConstNumber().getLongValue();
       long right = rightValue().getConstInstruction().asConstNumber().getLongValue();
       long result = foldLongs(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     } else if (type == NumericType.FLOAT) {
       float left = leftValue().getConstInstruction().asConstNumber().getFloatValue();
       float right = rightValue().getConstInstruction().asConstNumber().getFloatValue();
       float result = foldFloat(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
     } else {
       assert type == NumericType.DOUBLE;
       double left = leftValue().getConstInstruction().asConstNumber().getDoubleValue();
       double right = rightValue().getConstInstruction().asConstNumber().getDoubleValue();
       double result = foldDouble(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
     }
   }
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 5f1b33f..80100da 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
@@ -575,10 +575,6 @@
           for (Value value : instruction.getDebugValues()) {
             value.removeDebugUser(instruction);
           }
-          Value previousLocalValue = instruction.getPreviousLocalValue();
-          if (previousLocalValue != null) {
-            previousLocalValue.removeDebugUser(instruction);
-          }
         }
       }
     }
@@ -1144,7 +1140,7 @@
       // Remove the move-exception instruction.
       move = entry().asMoveException();
       position = move.getPosition();
-      assert move.getPreviousLocalValue() == null;
+      assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
     }
     // Create new predecessor blocks.
@@ -1158,8 +1154,7 @@
       BasicBlock newBlock = new BasicBlock();
       newPredecessors.add(newBlock);
       if (hasMoveException) {
-        Value value = new Value(
-            valueNumberGenerator.next(), MoveType.OBJECT, move.getDebugInfo());
+        Value value = new Value(valueNumberGenerator.next(), MoveType.OBJECT, move.getLocalInfo());
         values.add(value);
         MoveException newMove = new MoveException(value);
         newBlock.add(newMove);
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 968e560..140f579 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
@@ -114,10 +114,6 @@
     for (Value value : current.getDebugValues()) {
       value.removeDebugUser(current);
     }
-    Value previousLocalValue = current.getPreviousLocalValue();
-    if (previousLocalValue != null) {
-      previousLocalValue.removeDebugUser(current);
-    }
     listIterator.remove();
     current = null;
   }
@@ -350,7 +346,7 @@
     assert invoke.inValues().size() == arguments.size();
     for (int i = 0; i < invoke.inValues().size(); i++) {
       // TODO(zerny): Support inlining in --debug mode.
-      assert arguments.get(i).getDebugInfo() == null;
+      assert arguments.get(i).getLocalInfo() == null;
       if ((i == 0) && (downcast != null)) {
         Value invokeValue = invoke.inValues().get(0);
         Value receiverValue = arguments.get(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index 9b54353..aeb0a51 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -180,7 +180,7 @@
       }
     }
     assert result == -1 || result == 0 || result == 1;
-    Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+    Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
     return new ConstNumber(ConstType.INT, value, result);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 1880668..9f8dc4e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -33,10 +33,7 @@
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outType(),
-            original.getDebugInfo());
+        new Value(code.valueNumberGenerator.next(), original.outType(), original.getLocalInfo());
     return new ConstNumber(original.type, newValue, original.getRawValue());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 824d75c..21ed16e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -155,14 +156,11 @@
         values.add(phi);
         for (Value value : phi.getOperands()) {
           values.add(value);
-          if (value.isPhi()) {
-            Phi phiOperand = value.asPhi();
-            assert phiOperand.getBlock().getPhis().contains(phiOperand);
-            assert phiOperand.uniquePhiUsers().contains(phi);
-          } else {
-            Instruction definition = value.definition;
-            assert definition.outValue() == value;
-          }
+          assert value.uniquePhiUsers().contains(phi);
+        }
+        for (Value value : phi.getDebugValues()) {
+          values.add(value);
+          value.debugPhiUsers().contains(phi);
         }
       }
       for (Instruction instruction : block.getInstructions()) {
@@ -171,33 +169,41 @@
         if (outValue != null) {
           values.add(outValue);
           assert outValue.definition == instruction;
-          Value previousLocalValue = outValue.getPreviousLocalValue();
-          if (previousLocalValue != null) {
-            values.add(previousLocalValue);
-            assert previousLocalValue.debugUsers().contains(instruction);
-          }
         }
         for (Value value : instruction.inValues()) {
           values.add(value);
           assert value.uniqueUsers().contains(instruction);
-          if (value.isPhi()) {
-            Phi phi = value.asPhi();
-            assert phi.getBlock().getPhis().contains(phi);
-          } else {
-            Instruction definition = value.definition;
-            assert definition.outValue() == value;
-          }
+        }
+        for (Value value : instruction.getDebugValues()) {
+          values.add(value);
+          assert value.debugUsers().contains(instruction);
         }
       }
     }
 
     for (Value value : values) {
+      assert verifyValue(value);
       assert consistentValueUses(value);
     }
 
     return true;
   }
 
+  private boolean verifyValue(Value value) {
+    assert value.isPhi() ? verifyPhi(value.asPhi()) : verifyDefinition(value);
+    return true;
+  }
+
+  private boolean verifyPhi(Phi phi) {
+    assert phi.getBlock().getPhis().contains(phi);
+    return true;
+  }
+
+  private boolean verifyDefinition(Value value) {
+    assert value.definition.outValue() == value;
+    return true;
+  }
+
   private boolean consistentValueUses(Value value) {
     for (Instruction user : value.uniqueUsers()) {
       assert user.inValues().contains(value);
@@ -206,10 +212,13 @@
       assert phiUser.getOperands().contains(value);
       assert phiUser.getBlock().getPhis().contains(phiUser);
     }
-    if (value.debugUsers() != null) {
+    if (value.getLocalInfo() != null) {
       for (Instruction debugUser : value.debugUsers()) {
-        assert debugUser.getPreviousLocalValue() == value
-            || debugUser.getDebugValues().contains(value);
+        assert debugUser.getDebugValues().contains(value);
+      }
+      for (Phi phiUser : value.debugPhiUsers()) {
+        assert verifyPhi(phiUser);
+        assert phiUser.getDebugValues().contains(value);
       }
     }
     return true;
@@ -367,8 +376,8 @@
     return arguments;
   }
 
-  public Value createValue(MoveType moveType, Value.DebugInfo debugInfo) {
-    return new Value(valueNumberGenerator.next(), moveType, debugInfo);
+  public Value createValue(MoveType moveType, DebugLocalInfo local) {
+    return new Value(valueNumberGenerator.next(), moveType, local);
   }
 
   public Value createValue(MoveType moveType) {
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 e33c796..13325d8 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -65,10 +64,6 @@
     outValue = value;
     if (outValue != null) {
       outValue.definition = this;
-      Value previousLocalValue = getPreviousLocalValue();
-      if (previousLocalValue != null) {
-        previousLocalValue.addDebugUser(this);
-      }
     }
   }
 
@@ -107,27 +102,29 @@
     }
   }
 
-  public void replaceDebugPhi(Phi phi, Value value) {
-    if (debugValues != null) {
-      for (int i = 0; i < debugValues.size(); i++) {
-        if (phi == debugValues.get(i)) {
-          if (value.getLocalInfo() == null) {
-            debugValues.remove(i);
-          } else {
-            debugValues.set(i, value);
-            value.addDebugUser(this);
-          }
+  // Similar to Phi::replaceTrivialPhi, removal can cause a concurrent modification error.
+  // TODO(ager): Consider unifying with other replace methods and avoid the C.M.E.
+  public boolean replaceDebugValue(Value oldValue, Value newValue) {
+    if (debugValues == null) {
+      return false;
+    }
+    int found = -1;
+    for (int i = 0; i < debugValues.size(); i++) {
+      if (oldValue == debugValues.get(i)) {
+        assert found == -1;
+        found = i;
+        if (newValue.getLocalInfo() != null) {
+          // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
+          debugValues.set(i, newValue);
+          newValue.addDebugUser(this);
         }
       }
     }
-    if (phi == getPreviousLocalValue()) {
-      if (value.getDebugInfo() == null) {
-        replacePreviousLocalValue(null);
-      } else {
-        replacePreviousLocalValue(value);
-        value.addDebugUser(this);
-      }
+    if (found >= 0 && newValue.getLocalInfo() == null) {
+      // TODO(zerny): Insert a write if replacing a phi with associated debug-local info.
+      debugValues.remove(found);
     }
+    return found >= 0;
   }
 
   /**
@@ -323,26 +320,14 @@
 
   public abstract int maxOutValueRegister();
 
-  public DebugInfo getDebugInfo() {
-    return outValue == null ? null : outValue.getDebugInfo();
-  }
-
   public DebugLocalInfo getLocalInfo() {
     return outValue == null ? null : outValue.getLocalInfo();
   }
 
-  public Value getPreviousLocalValue() {
-    return outValue == null ? null : outValue.getPreviousLocalValue();
-  }
-
   public List<Value> getDebugValues() {
     return debugValues != null ? debugValues : ImmutableList.of();
   }
 
-  public void replacePreviousLocalValue(Value value) {
-    outValue.replacePreviousLocalValue(value);
-  }
-
   public boolean isArrayGet() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 52a0f55..770278a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -37,7 +37,7 @@
       int left = leftValue().getConstInstruction().asConstNumber().getIntValue();
       int right = rightValue().getConstInstruction().asConstNumber().getIntValue();
       int result = foldIntegers(left, right);
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else {
       assert type == NumericType.LONG;
@@ -50,7 +50,7 @@
         right = rightValue().getConstInstruction().asConstNumber().getLongValue();
       }
       long result = foldLongs(left, right);
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 59022de..f858d70 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -31,20 +31,20 @@
     assert canBeFolded();
     if (type == NumericType.INT) {
       int result = -source().getConstInstruction().asConstNumber().getIntValue();
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else if (type == NumericType.LONG) {
       long result = -source().getConstInstruction().asConstNumber().getLongValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     } else if (type == NumericType.FLOAT) {
       float result = -source().getConstInstruction().asConstNumber().getFloatValue();
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.FLOAT, value, Float.floatToIntBits(result));
     } else {
       assert type == NumericType.DOUBLE;
       double result = -source().getConstInstruction().asConstNumber().getDoubleValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.DOUBLE, value, Double.doubleToLongBits(result));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index cae6118..4c42b80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -27,12 +27,12 @@
     assert canBeFolded();
     if (type == NumericType.INT) {
       int result = ~(source().getConstInstruction().asConstNumber().getIntValue());
-      Value value = code.createValue(MoveType.SINGLE, getDebugInfo());
+      Value value = code.createValue(MoveType.SINGLE, getLocalInfo());
       return new ConstNumber(ConstType.INT, value, result);
     } else {
       assert type == NumericType.LONG;
       long result = ~source().getConstInstruction().asConstNumber().getLongValue();
-      Value value = code.createValue(MoveType.WIDE, getDebugInfo());
+      Value value = code.createValue(MoveType.WIDE, getLocalInfo());
       return new ConstNumber(ConstType.LONG, value, result);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 7e05dc9..185d2fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -20,6 +21,7 @@
 
   private final BasicBlock block;
   private final List<Value> operands = new ArrayList<>();
+  private List<Value> debugValues = null;
 
   // Trivial phis are eliminated during IR construction. When a trivial phi is eliminated
   // we need to update all references to it. A phi can be referenced from phis, instructions
@@ -36,7 +38,7 @@
   private MoveType outType = null;
 
   public Phi(int number, BasicBlock block, MoveType type, DebugLocalInfo local) {
-    super(number, type, local == null ? null : new DebugInfo(local, null));
+    super(number, type, local);
     this.block = block;
     block.addPhi(this);
   }
@@ -96,6 +98,15 @@
     removeTrivialPhi();
   }
 
+  public void addDebugValue(Value value) {
+    assert value.getLocalInfo() != null;
+    if (debugValues == null) {
+      debugValues = new ArrayList<>();
+    }
+    debugValues.add(value);
+    value.addDebugPhiUser(this);
+  }
+
   private void throwUndefinedValueError() {
     throw new CompilationError(
         "Undefined value encountered during compilation. "
@@ -157,6 +168,20 @@
     }
   }
 
+  private void replaceTrivialDebugPhi(Value current, Value newValue) {
+    if (debugValues == null) {
+      return;
+    }
+    assert current.getLocalInfo() != null;
+    assert current.getLocalInfo() == newValue.getLocalInfo();
+    for (int i = 0; i < debugValues.size(); i++) {
+      if (debugValues.get(i) == current) {
+        debugValues.set(i, newValue);
+        newValue.addDebugPhiUser(this);
+      }
+    }
+  }
+
   public boolean isTrivialPhi() {
     Value same = null;
     for (Value op : operands) {
@@ -202,8 +227,15 @@
       user.replaceTrivialPhi(this, same);
     }
     if (debugUsers() != null) {
+      List<Instruction> removed = new ArrayList<>();
       for (Instruction user : debugUsers()) {
-        user.replaceDebugPhi(this, same);
+        if (user.replaceDebugValue(this, same)) {
+          removed.add(user);
+        }
+      }
+      removed.forEach(this::removeDebugUser);
+      for (Phi user : debugPhiUsers()) {
+        user.replaceTrivialDebugPhi(this, same);
       }
     }
     // If IR construction is taking place, update the definition users.
@@ -316,4 +348,8 @@
   public boolean needsRegister() {
     return true;
   }
+
+  public List<Value> getDebugValues() {
+    return debugValues != null ? debugValues : ImmutableList.of();
+  }
 }
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 44bd9d4..78cb4a5 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
@@ -18,39 +18,17 @@
 
 public class Value {
 
-  /**
-   * Immutable view of the debug info associated with an SSA value.
-   *
-   * Used during IR building and to construct replacement values.
-   */
-  public static class DebugInfo {
-    private final DebugLocalInfo local;
-    private final Value previousLocalValue;
-
-    public DebugInfo(DebugLocalInfo local, Value previousLocalValue) {
-      assert local != null;
-      this.local = local;
-      this.previousLocalValue = previousLocalValue;
-    }
-  }
-
-  // Actual internal data for the debug information of locals.
+  // 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;
-    Value previousLocalValue;
-    Set<Instruction> debugUsers = new HashSet<>();
+    Set<Instruction> users = new HashSet<>();
+    Set<Phi> phiUsers = new HashSet<>();
     List<Instruction> localStarts = new ArrayList<>();
     List<Instruction> localEnds = new ArrayList<>();
 
-    DebugData(DebugInfo info) {
-      this(info.local, info.previousLocalValue);
-    }
-
-    DebugData(DebugLocalInfo local, Value previousLocalValue) {
-      assert previousLocalValue == null || !previousLocalValue.isUninitializedLocal();
+    DebugData(DebugLocalInfo local) {
       this.local = local;
-      this.previousLocalValue = previousLocalValue;
     }
   }
 
@@ -73,10 +51,10 @@
   private LongInterval valueRange;
   private final DebugData debugData;
 
-  public Value(int number, MoveType type, DebugInfo debugInfo) {
+  public Value(int number, MoveType type, DebugLocalInfo local) {
     this.number = number;
     this.type = type;
-    this.debugData = debugInfo == null ? null : new DebugData(debugInfo);
+    this.debugData = local == null ? null : new DebugData(local);
   }
 
   public boolean isFixedRegisterValue() {
@@ -95,26 +73,10 @@
     return type.requiredRegisters();
   }
 
-  public DebugInfo getDebugInfo() {
-    return debugData == null ? null : new DebugInfo(debugData.local, debugData.previousLocalValue);
-  }
-
   public DebugLocalInfo getLocalInfo() {
     return debugData == null ? null : debugData.local;
   }
 
-  public Value getPreviousLocalValue() {
-    return debugData == null ? null : debugData.previousLocalValue;
-  }
-
-  public void replacePreviousLocalValue(Value value) {
-    if (value == null || value.isUninitializedLocal()) {
-      debugData.previousLocalValue = null;
-    } else {
-      debugData.previousLocalValue = value;
-    }
-  }
-
   public List<Instruction> getDebugLocalStarts() {
     return debugData.localStarts;
   }
@@ -189,10 +151,11 @@
   }
 
   public Set<Instruction> debugUsers() {
-    if (debugData == null) {
-      return null;
-    }
-    return Collections.unmodifiableSet(debugData.debugUsers);
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
+  }
+
+  public Set<Phi> debugPhiUsers() {
+    return debugData == null ? null : Collections.unmodifiableSet(debugData.phiUsers);
   }
 
   public int numberOfUsers() {
@@ -211,18 +174,30 @@
     return uniquePhiUsers().size();
   }
 
+  public int numberOfAllNonDebugUsers() {
+    return numberOfUsers() + numberOfPhiUsers();
+  }
+
   public int numberOfDebugUsers() {
-    return debugData == null ? 0 : debugData.debugUsers.size();
+    return debugData == null ? 0 : debugData.users.size();
+  }
+
+  public int numberOfDebugPhiUsers() {
+    return debugData == null ? 0 : debugData.phiUsers.size();
+  }
+
+  public int numberOfAllDebugUsers() {
+    return numberOfDebugUsers() + numberOfDebugPhiUsers();
   }
 
   public int numberOfAllUsers() {
-    return numberOfUsers() + numberOfPhiUsers() + numberOfDebugUsers();
+    return numberOfAllNonDebugUsers() + numberOfAllDebugUsers();
   }
 
   public boolean isUsed() {
     return !users.isEmpty()
         || !phiUsers.isEmpty()
-        || ((debugData != null) && !debugData.debugUsers.isEmpty());
+        || ((debugData != null) && !debugData.users.isEmpty());
   }
 
   public boolean usedInMonitorOperation() {
@@ -250,7 +225,7 @@
     phiUsers.clear();
     uniquePhiUsers = null;
     if (debugData != null) {
-      debugData.debugUsers.clear();
+      debugData.users.clear();
     }
   }
 
@@ -268,7 +243,14 @@
     if (isUninitializedLocal()) {
       return;
     }
-    debugData.debugUsers.add(user);
+    debugData.users.add(user);
+  }
+
+  public void addDebugPhiUser(Phi user) {
+    if (isUninitializedLocal()) {
+      return;
+    }
+    debugData.phiUsers.add(user);
   }
 
   public boolean isUninitializedLocal() {
@@ -280,7 +262,11 @@
   }
 
   public void removeDebugUser(Instruction user) {
-    debugData.debugUsers.remove(user);
+    debugData.users.remove(user);
+  }
+
+  public void removeDebugPhiUser(Phi user) {
+    debugData.phiUsers.remove(user);
   }
 
   public boolean hasUsersInfo() {
@@ -293,7 +279,8 @@
     phiUsers = null;
     uniquePhiUsers = null;
     if (debugData != null) {
-      debugData.debugUsers = null;
+      debugData.users = null;
+      debugData.phiUsers = null;
     }
   }
 
@@ -328,10 +315,15 @@
           }
           return v;
         });
-        if (user.getPreviousLocalValue() == this) {
-          newValue.addDebugUser(user);
-          user.replacePreviousLocalValue(newValue);
-        }
+      }
+      for (Phi user : debugPhiUsers()) {
+        user.getDebugValues().replaceAll(v -> {
+          if (v == this) {
+            newValue.addDebugPhiUser(user);
+            return newValue;
+          }
+          return v;
+        });
       }
     }
     clearUsers();
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 976020c2..1f29d26 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
@@ -75,7 +75,6 @@
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Ushr;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.Value.DebugInfo;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.utils.InternalOptions;
@@ -269,6 +268,7 @@
   private final InternalOptions options;
 
   // Pending local changes.
+  private Value previousLocalValue = null;
   private List<Value> debugLocalStarts = new ArrayList<>();
   private List<Value> debugLocalReads = new ArrayList<>();
   private List<Value> debugLocalEnds = new ArrayList<>();
@@ -489,16 +489,14 @@
 
   public void addThisArgument(int register) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = local == null ? null : new DebugInfo(local, null);
-    Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, info);
+    Value value = writeRegister(register, MoveType.OBJECT, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
     value.markAsThis();
   }
 
   public void addNonThisArgument(int register, MoveType moveType) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = local == null ? null : new DebugInfo(local, null);
-    Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, info);
+    Value value = writeRegister(register, moveType, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
   }
 
@@ -1096,7 +1094,7 @@
 
   public void addMoveException(int dest) {
     Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.NO_THROW);
-    assert out.getDebugInfo() == null;
+    assert out.getLocalInfo() == null;
     MoveException instruction = new MoveException(out);
     assert !instruction.instructionTypeCanThrow();
     if (!currentBlock.getInstructions().isEmpty()) {
@@ -1501,21 +1499,18 @@
 
   // This special write register is needed when changing the scoping of a local variable.
   // See addDebugLocalStart and addDebugLocalEnd.
-  private Value writeRegister(int register, MoveType type, ThrowingInfo throwing, DebugInfo info) {
+  private Value writeRegister(
+      int register, MoveType type, ThrowingInfo throwing, DebugLocalInfo local) {
     checkRegister(register);
-    Value value = new Value(valueNumberGenerator.next(), type, info);
+    Value value = new Value(valueNumberGenerator.next(), type, local);
     currentBlock.writeCurrentDefinition(register, value, throwing);
     return value;
   }
 
   public Value writeRegister(int register, MoveType type, ThrowingInfo throwing) {
     DebugLocalInfo local = getCurrentLocal(register);
-    DebugInfo info = null;
-    if (local != null) {
-      Value previousLocal = readRegisterIgnoreLocal(register, type);
-      info = new DebugInfo(local, previousLocal.getLocalInfo() != local ? null : previousLocal);
-    }
-    return writeRegister(register, type, throwing, info);
+    previousLocalValue = local == null ? null : readRegisterIgnoreLocal(register, type);
+    return writeRegister(register, type, throwing, local);
   }
 
   public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
@@ -1596,6 +1591,12 @@
     if (!options.debug) {
       return;
     }
+    // Add a use if this instruction is overwriting a previous value of the same local.
+    if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) {
+      assert ir.outValue() != null;
+      ir.addDebugValue(previousLocalValue);
+    }
+    previousLocalValue = null;
     if (debugLocalStarts.isEmpty() && debugLocalReads.isEmpty() && debugLocalEnds.isEmpty()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index ca1d53c..c21c090 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -260,7 +260,8 @@
   @Override
   public void buildPrelude(IRBuilder builder) {
     // Record types for arguments.
-    Map<Integer, MoveType> initializedLocals = recordArgumentTypes();
+    Int2ReferenceMap<MoveType> argumentLocals = recordArgumentTypes();
+    Int2ReferenceMap<MoveType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
     // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
@@ -294,7 +295,11 @@
     }
     // Add debug information for all locals at the initial label.
     if (initialLabel != null) {
-      state.openLocals(initialLabel);
+      for (Local local : state.openLocals(initialLabel)) {
+        if (!argumentLocals.containsKey(local.slot.register)) {
+          builder.addDebugLocalStart(local.slot.register, local.info);
+        }
+      }
     }
     // Build the actual argument instructions now that type and debug information is known
     // for arguments.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index a7c40d1..71de2b6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -51,7 +51,7 @@
     if (insn.outValue() == null) {
       return null;
     } else {
-      return code.createValue(insn.outType(), insn.getDebugInfo());
+      return code.createValue(insn.outType(), insn.getLocalInfo());
     }
   }
 
@@ -105,7 +105,7 @@
             // Fix up the return type if needed.
             if (actualTarget.proto.returnType != invokedMethod.proto.returnType
                 && newInvoke.outValue() != null) {
-              Value newValue = code.createValue(newInvoke.outType(), invoke.getDebugInfo());
+              Value newValue = code.createValue(newInvoke.outType(), invoke.getLocalInfo());
               newInvoke.outValue().replaceUsers(newValue);
               CheckCast cast = new CheckCast(
                   newValue,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 3a1902e..50f224c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -61,10 +61,6 @@
     for (Value debugValue : instruction.getDebugValues()) {
       updateWorklist(worklist, debugValue);
     }
-    Value previousLocalValue = instruction.getPreviousLocalValue();
-    if (previousLocalValue != null) {
-      updateWorklist(worklist, previousLocalValue);
-    }
   }
 
   private static void removeDeadPhis(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index a34c8cb..241106c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -75,7 +75,7 @@
     MoveType moveType = instruction.outValue().outType();
     if (rule != null && rule.hasReturnValue() && rule.getReturnValue().isSingleValue()) {
       assert moveType != MoveType.OBJECT;
-      Value value = code.createValue(moveType, instruction.getDebugInfo());
+      Value value = code.createValue(moveType, instruction.getLocalInfo());
       replacement = new ConstNumber(
           ConstType.fromMoveType(moveType), value, rule.getReturnValue().getSingleValue());
     }
@@ -84,7 +84,7 @@
       DexField field = rule.getReturnValue().getField();
       DexEncodedField staticField = appInfo.lookupStaticTarget(field.clazz, field);
       if (staticField != null) {
-        Value value = code.createValue(moveType, instruction.getDebugInfo());
+        Value value = code.createValue(moveType, instruction.getLocalInfo());
         replacement = staticField.staticValue.asConstInstruction(false, value);
       } else {
         throw new CompilationError(field.clazz.toSourceString() + "." + field.name.toString() +
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 460fe92..6cd453b 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
@@ -1841,6 +1841,8 @@
         int predIndex = succ.getPredecessors().indexOf(block);
         for (Phi phi : succ.getPhis()) {
           live.add(phi.getOperand(predIndex));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          live.addAll(phi.getDebugValues());
         }
       }
       ListIterator<Instruction> iterator =
@@ -1855,17 +1857,8 @@
             live.add(use);
           }
         }
-        if (options.debug) {
-          for (Value use : instruction.getDebugValues()) {
-            assert use.needsRegister();
-            live.add(use);
-          }
-          Value use = instruction.getPreviousLocalValue();
-          if (use != null) {
-            assert use.needsRegister();
-            live.add(use);
-          }
-        }
+        assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
+        live.addAll(instruction.getDebugValues());
       }
       for (Phi phi : block.getPhis()) {
         live.remove(phi);
@@ -1942,6 +1935,8 @@
         for (Phi phi : successor.getPhis()) {
           live.remove(phi);
           phiOperands.add(phi.getOperand(successor.getPredecessors().indexOf(block)));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          phiOperands.addAll(phi.getDebugValues());
         }
       }
       live.addAll(phiOperands);
@@ -1990,14 +1985,6 @@
               addLiveRange(use, block, number);
             }
           }
-          Value use = instruction.getPreviousLocalValue();
-          if (use != null) {
-            assert use.needsRegister();
-            if (!live.contains(use)) {
-              live.add(use);
-              addLiveRange(use, block, number);
-            }
-          }
         }
       }
     }