Merge "Lazily introduce values for uninitialized debug locals."
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 98b4502..e66ddfc 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
@@ -87,6 +87,7 @@
 import com.android.tools.r8.utils.Pair;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
@@ -309,6 +310,7 @@
   // Basic blocks. Added after processing from the worklist.
   private final LinkedList<BasicBlock> blocks = new LinkedList<>();
 
+  private BasicBlock entryBlock = null;
   private BasicBlock currentBlock = null;
   final private ValueNumberGenerator valueNumberGenerator;
   private final DexEncodedMethod method;
@@ -324,6 +326,9 @@
   private Value previousLocalValue = null;
   private final List<Value> debugLocalReads = new ArrayList<>();
 
+  // Lazily populated list of local values that are referenced without being actually defined.
+  private Int2ReferenceMap<List<Value>> uninitializedDebugLocalValues = null;
+
   private int nextBlockNumber = 0;
 
   // Flag indicating if the instructions define values with imprecise types.
@@ -420,6 +425,7 @@
     processedInstructions = null;
 
     setCurrentBlock(targets.get(INITIAL_BLOCK_OFFSET).block);
+    entryBlock = currentBlock;
     source.buildPrelude(this);
 
     // Process normal blocks reachable from the entry block using a worklist of reachable
@@ -438,6 +444,21 @@
     // Insert debug positions so all position changes are marked by an explicit instruction.
     boolean hasDebugPositions = insertDebugPositions();
 
+    // Insert definitions for all uninitialized local values.
+    if (uninitializedDebugLocalValues != null) {
+      InstructionListIterator it = entryBlock.listIterator();
+      it.nextUntil(i -> !i.isArgument());
+      it.previous();
+      for (List<Value> values : uninitializedDebugLocalValues.values()) {
+        for (Value value : values) {
+          Instruction def = new DebugLocalUninitialized(value);
+          def.setBlock(entryBlock);
+          def.setPosition(Position.none());
+          it.add(def);
+        }
+      }
+    }
+
     // Clear all reaching definitions to free up memory (and avoid invalid use).
     for (BasicBlock block : blocks) {
       block.clearCurrentDefinitions();
@@ -648,15 +669,6 @@
     addInstruction(new Argument(value));
   }
 
-  public void addDebugUninitialized(int register, ValueType type) {
-    if (!options.debug) {
-      return;
-    }
-    Value value = writeRegister(register, type, ThrowingInfo.NO_THROW, null);
-    assert !value.hasLocalInfo();
-    addInstruction(new DebugLocalUninitialized(value));
-  }
-
   private void addDebugLocalWrite(ValueType type, int dest, Value in) {
     assert options.debug;
     Value out = writeRegister(dest, type, ThrowingInfo.NO_THROW);
@@ -670,7 +682,7 @@
     assert local != null;
     assert local == getIncomingLocal(register);
     ValueType valueType = ValueType.fromDexType(local.type);
-    return readRegisterIgnoreLocal(register, valueType);
+    return readRegisterIgnoreLocal(register, valueType, local);
   }
 
   private static boolean isValidFor(Value value, DebugLocalInfo local) {
@@ -697,7 +709,7 @@
     assert local == getOutgoingLocal(register);
     ValueType valueType = ValueType.fromDexType(local.type);
     // TODO(mathiasr): Here we create a Phi with type based on debug info. That's just wrong!
-    Value incomingValue = readRegisterIgnoreLocal(register, valueType);
+    Value incomingValue = readRegisterIgnoreLocal(register, valueType, local);
 
     // TODO(mathiasr): This can be simplified once trivial phi removal is local-info aware.
     if (incomingValue.isPhi() || incomingValue.getLocalInfo() != local) {
@@ -1672,8 +1684,7 @@
     return value;
   }
 
-  private Value readRegisterIgnoreLocal(int register, ValueType type) {
-    DebugLocalInfo local = getIncomingLocal(register);
+  private Value readRegisterIgnoreLocal(int register, ValueType type, DebugLocalInfo local) {
     return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local);
   }
 
@@ -1707,7 +1718,14 @@
     }
     // If the register still has unknown value create a phi value for it.
     if (value == null) {
-      if (!block.isSealed()) {
+      if (block == entryBlock && local != null) {
+        assert block.getPredecessors().isEmpty();
+        // If a debug-local is referenced at the entry block, lazily introduce an SSA value for it.
+        // This value must *not* be added to the entry blocks current definitions since
+        // uninitialized debug-locals may be reference at the same register/local-index yet be of
+        // different types (eg, int in one part of the CFG and float in a disjoint part).
+        value = getUninitializedDebugLocalValue(register, type, local);
+      } else if (!block.isSealed()) {
         assert !blocks.isEmpty() : "No write to " + register;
         Phi phi = new Phi(valueNumberGenerator.next(), block, type, local);
         block.addIncompletePhi(register, phi, readingEdge);
@@ -1734,6 +1752,29 @@
     return value;
   }
 
+  private Value getUninitializedDebugLocalValue(
+      int register, ValueType type, DebugLocalInfo local) {
+    assert type == ValueType.fromDexType(local.type);
+    if (uninitializedDebugLocalValues == null) {
+      uninitializedDebugLocalValues = new Int2ReferenceOpenHashMap<>();
+    }
+    List<Value> values = uninitializedDebugLocalValues.get(register);
+    if (values != null) {
+      for (Value value : values) {
+        if (value.outType() == type) {
+          return value;
+        }
+      }
+    } else {
+      uninitializedDebugLocalValues.put(register, new ArrayList<>(2));
+    }
+    // Create a new SSA value for the uninitialized local value.
+    // Note that the uninitialized local value *does not* itself have local information!
+    Value value = new Value(valueNumberGenerator.next(), type, null);
+    uninitializedDebugLocalValues.get(register).add(value);
+    return value;
+  }
+
   public Value readNumericRegister(int register, NumericType type) {
     return readRegister(register, ValueType.fromNumericType(type));
   }
@@ -1785,7 +1826,7 @@
     previousLocalValue =
         (incomingLocal == null || incomingLocal != outgoingLocal)
             ? null
-            : readRegisterIgnoreLocal(register, type);
+            : readRegisterIgnoreLocal(register, type, incomingLocal);
     return writeRegister(register, type, throwing, outgoingLocal);
   }
 
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 85a0f99..01c4dd0 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
@@ -29,7 +29,6 @@
 import com.android.tools.r8.ir.conversion.JarState.Slot;
 import com.android.tools.r8.logging.Log;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.io.PrintWriter;
@@ -275,7 +274,6 @@
     // Record types for arguments.
     Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
     Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
-    Int2ReferenceMap<ValueType> uninitializedLocals = new Int2ReferenceOpenHashMap<>();
     // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
@@ -320,11 +318,9 @@
       int localRegister = state.getLocalRegister(local.index, localType);
       ValueType existingLocalType = initializedLocals.get(localRegister);
       if (existingLocalType == null) {
-        // For uninitialized entries write the local to ensure it exists in the local state.
         int writeRegister = state.writeLocal(local.index, localType);
         assert writeRegister == localRegister;
         initializedLocals.put(localRegister, localValueType);
-        uninitializedLocals.put(localRegister, localValueType);
       }
     }
 
@@ -335,10 +331,6 @@
     // for arguments.
     buildArgumentInstructions(builder);
 
-    for (Entry<ValueType> entry : uninitializedLocals.int2ReferenceEntrySet()) {
-      builder.addDebugUninitialized(entry.getIntKey(), entry.getValue());
-    }
-
     // Add debug information for all locals at the initial label.
     for (Local local : state.getLocalsToOpen()) {
       if (!argumentLocals.containsKey(local.slot.register)) {