Allow treating DebugLocalChange instructions as identical if they are.

This enables common suffix sharing for spill moves inserted in catch
blocks. Previously, a DebugLocalChange event at the end of the moves
would prevent us from sharing any of the moves.

This saves around 30kB on the dexed framework.

R=zerny@google.com

Change-Id: I06ae0bbe1542b11bcec0b8bb6b560c1a2fe64754
diff --git a/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java b/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java
index 4d7d120..bf39f6f 100644
--- a/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DebugLocalInfo.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+
 public class DebugLocalInfo {
   public final DexString name;
   public final DexType type;
@@ -14,6 +16,25 @@
     this.signature = signature;
   }
 
+  public static boolean localsInfoMapsEqual(
+      Int2ReferenceMap<DebugLocalInfo> set0, Int2ReferenceMap<DebugLocalInfo> set1) {
+    if (set0 == null) {
+      return set1 == null;
+    }
+    if (set1 == null) {
+      return false;
+    }
+    if (set0.keySet().size() != set1.keySet().size()) {
+      return false;
+    }
+    for (int i : set0.keySet()) {
+      if (!set0.get(i).equals(set1.get(i))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   @Override
   public boolean equals(Object other) {
     if (this == other) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 55bb03d..f579fd6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -52,7 +52,9 @@
   @Override
   public boolean identicalNonValueParts(Instruction other) {
     assert other.isDebugLocalsChange();
-    return false;
+    DebugLocalsChange o = (DebugLocalsChange) other;
+    return DebugLocalInfo.localsInfoMapsEqual(ending, o.ending)
+        && DebugLocalInfo.localsInfoMapsEqual(starting, o.starting);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index 9a70864..4ad05cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -3,8 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.DebugLocalsChange;
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -15,6 +17,9 @@
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence.Wrapper;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -99,16 +104,30 @@
   }
 
   private static BasicBlock createAndInsertBlockForSuffix(
-      int blockNumber, int suffixSize, List<BasicBlock> preds, BasicBlock block) {
+      int blockNumber, int suffixSize, List<BasicBlock> preds, BasicBlock successorBlock) {
     BasicBlock newBlock = BasicBlock.createGotoBlock(blockNumber);
     BasicBlock first = preds.get(0);
     InstructionListIterator from = first.listIterator(first.getInstructions().size() - 1);
+    Int2ReferenceMap<DebugLocalInfo> newBlockEntryLocals = successorBlock.getLocalsAtEntry() == null
+        ? null
+        : new Int2ReferenceOpenHashMap<>(successorBlock.getLocalsAtEntry());
     boolean movedThrowingInstruction = false;
     for (int i = 0; i < suffixSize; i++) {
       Instruction instruction = from.previous();
       movedThrowingInstruction = movedThrowingInstruction || instruction.instructionTypeCanThrow();
       newBlock.getInstructions().addFirst(instruction);
       instruction.setBlock(newBlock);
+      if (instruction.isDebugLocalsChange()) {
+        // Replay the debug local changes backwards to compute the entry state.
+        assert newBlockEntryLocals != null;
+        DebugLocalsChange change = instruction.asDebugLocalsChange();
+        for (int starting : change.getStarting().keySet()) {
+          newBlockEntryLocals.remove(starting);
+        }
+        for (Entry<DebugLocalInfo> ending : change.getEnding().int2ReferenceEntrySet()) {
+          newBlockEntryLocals.put(ending.getIntKey(), ending.getValue());
+        }
+      }
     }
     if (movedThrowingInstruction && first.hasCatchHandlers()) {
       newBlock.transferCatchHandlers(first);
@@ -121,13 +140,14 @@
       }
       instructions.add(exit);
       newBlock.getPredecessors().add(pred);
-      pred.replaceSuccessor(block, newBlock);
-      block.getPredecessors().remove(pred);
+      pred.replaceSuccessor(successorBlock, newBlock);
+      successorBlock.getPredecessors().remove(pred);
       if (movedThrowingInstruction) {
         pred.clearCatchHandlers();
       }
     }
-    newBlock.link(block);
+    newBlock.setLocalsAtEntry(newBlockEntryLocals);
+    newBlock.link(successorBlock);
     return newBlock;
   }