Ensure initial local state for all locals before opening their scope.

R=ager, shertz

Bug: 64752164
Change-Id: I3b30cc356b28511ec28ae931d9d50c07aabd9e8d
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 8d36ef7..ca1d53c 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
@@ -31,6 +31,8 @@
 import com.android.tools.r8.ir.conversion.JarState.Local;
 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.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -257,9 +259,39 @@
 
   @Override
   public void buildPrelude(IRBuilder builder) {
-    Map<Integer, MoveType> initializedLocals = new HashMap<>(node.localVariables.size());
     // Record types for arguments.
-    recordArgumentTypes(initializedLocals);
+    Map<Integer, MoveType> initializedLocals = recordArgumentTypes();
+    // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
+    for (Object o : node.localVariables) {
+      LocalVariableNode local = (LocalVariableNode) o;
+      Type localType = Type.getType(local.desc);
+      int sort = localType.getSort();
+      switch (sort) {
+        case Type.OBJECT:
+        case Type.ARRAY:
+          localType = JarState.NULL_TYPE;
+          break;
+        case Type.DOUBLE:
+        case Type.LONG:
+        case Type.FLOAT:
+          break;
+        case Type.VOID:
+        case Type.METHOD:
+          throw new Unreachable("Invalid local variable type: " + localType);
+        default:
+          localType = Type.INT_TYPE;
+          break;
+      }
+      int localRegister = state.getLocalRegister(local.index, localType);
+      MoveType existingLocalType = initializedLocals.get(localRegister);
+      assert existingLocalType == null || existingLocalType == moveType(localType);
+      if (existingLocalType == null) {
+        int localRegister2 = state.writeLocal(local.index, localType);
+        assert localRegister == localRegister2;
+        initializedLocals.put(localRegister, moveType(localType));
+        builder.addDebugUninitialized(localRegister, constType(localType));
+      }
+    }
     // Add debug information for all locals at the initial label.
     if (initialLabel != null) {
       state.openLocals(initialLabel);
@@ -285,37 +317,6 @@
       monitorEnter = builder.addMonitor(Monitor.Type.ENTER, monitorRegister);
       generatingMethodSynchronization = false;
     }
-    // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
-    for (Object o : node.localVariables) {
-      LocalVariableNode local = (LocalVariableNode) o;
-      Type localType = Type.getType(local.desc);
-      int sort = localType.getSort();
-      switch (sort) {
-        case Type.OBJECT:
-        case Type.ARRAY:
-          localType = JarState.NULL_TYPE;
-          break;
-        case Type.DOUBLE:
-        case Type.LONG:
-        case Type.FLOAT:
-          break;
-        case Type.VOID:
-        case Type.METHOD:
-          throw new Unreachable("Invalid local variable type: " + localType);
-        default:
-          localType = Type.INT_TYPE;
-          break;
-      }
-      int localRegister = state.getLocalRegister(local.index, localType);
-      MoveType exitingLocalType = initializedLocals.get(localRegister);
-      assert exitingLocalType == null || exitingLocalType == moveType(localType);
-      if (exitingLocalType == null) {
-        int localRegister2 = state.writeLocal(local.index, localType);
-        assert localRegister == localRegister2;
-        initializedLocals.put(localRegister, moveType(localType));
-        builder.addDebugUninitialized(localRegister, constType(localType));
-      }
-    }
     computeBlockEntryJarStates(builder);
     state.setBuilding();
   }
@@ -335,7 +336,9 @@
     }
   }
 
-  private void recordArgumentTypes(Map<Integer, MoveType> initializedLocals) {
+  private Int2ReferenceMap<MoveType> recordArgumentTypes() {
+    Int2ReferenceMap<MoveType> initializedLocals =
+        new Int2ReferenceOpenHashMap<>(node.localVariables.size());
     int argumentRegister = 0;
     if (!isStatic()) {
       Type thisType = Type.getType(clazz.descriptor.toString());
@@ -348,6 +351,7 @@
       argumentRegister += moveType.requiredRegisters();
       initializedLocals.put(register, moveType);
     }
+    return initializedLocals;
   }
 
   private void computeBlockEntryJarStates(IRBuilder builder) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 2a3e431..03c614a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -344,14 +344,7 @@
 
   private Local setLocalInfoForRegister(int register, DebugLocalInfo info) {
     Local existingLocal = getLocalForRegister(register);
-    // TODO(ager, zerny): Kotlin debug information contains locals that are not referenced.
-    // That seems broken and we currently do not retain that debug information because
-    // we do not let locals debug information influence code generation. Debug information can
-    // be completely malformed, so we shouldn't let it influence code generation. However, we
-    // need to deal with these unused locals in the debug information. For now we
-    // use a null type for the slot, but we should reconsider that.
-    Slot slot = existingLocal != null ? existingLocal.slot : new Slot(register, null);
-    Local local = new Local(slot, info);
+    Local local = new Local(existingLocal.slot, info);
     locals[register] = local;
     return local;
   }