Merge "Test debug stepping in <clinit>"
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index ffaf42b..3d68ace 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -91,8 +91,8 @@
       // If this is the end of the block clear out the pending state.
       pendingLocals = null;
       pendingLocalChanges = false;
-    } else {
-      // For non-exit / pc-advancing instructions emit any pending changes.
+    } else if (pc != emittedPc) {
+      // For non-exit / pc-advancing instructions emit any pending changes once possible.
       emitLocalChanges(pc);
     }
   }
@@ -224,6 +224,7 @@
       DexString nextFile,
       List<DexDebugEvent> events,
       DexItemFactory factory) {
+    assert previousPc != nextPc;
     int pcDelta = previousPc == NO_PC_INFO ? nextPc : nextPc - previousPc;
     int lineDelta = nextLine == NO_LINE_INFO ? 0 : nextLine - previousLine;
     assert pcDelta >= 0;
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 31201a2..dafaa26 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
@@ -2307,8 +2307,19 @@
     }
   }
 
+  private boolean isExitingThrow(InsnNode insn) {
+    List<TryCatchBlock> handlers = getTryHandlers(insn);
+    if (handlers.isEmpty()) {
+      return true;
+    }
+    if (!isSynchronized() || handlers.size() > 1) {
+      return false;
+    }
+    return handlers.get(0) == EXCEPTIONAL_SYNC_EXIT;
+  }
+
   private void addThrow(InsnNode insn, int register, IRBuilder builder) {
-    if (getTryHandlers(insn).isEmpty()) {
+    if (isExitingThrow(insn)) {
       processLocalVariablesAtExit(insn, builder);
     } else {
       processLocalVariablesAtControlEdge(insn, builder);
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 a7d6e8d..35cadf2 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
@@ -84,7 +84,9 @@
             commonSuffixSize = Math.min(
                 commonSuffixSize, sharedSuffixSizeExcludingExit(firstPred, pred, allocator));
           }
-          assert commonSuffixSize >= 1;
+          if (commonSuffixSize == 0) {
+            continue;
+          }
           int blockNumber = startNumberOfNewBlock + newBlocks.size();
           BasicBlock newBlock = createAndInsertBlockForSuffix(
               blockNumber, commonSuffixSize, predsWithSameLastInstruction, block);
@@ -160,7 +162,9 @@
       Instruction i0 = it0.previous();
       Instruction i1 = it1.previous();
       if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
-        return suffixSize;
+        // If the shared suffix follows a debug position at least one instruction must remain
+        // unshared to ensure the debug position is at a different pc than the shared suffix.
+        return i0.isDebugPosition() || i1.isDebugPosition() ? suffixSize - 1 : suffixSize;
       }
       suffixSize++;
     }
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 9b3eb83..d6a64a4 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
@@ -24,7 +24,6 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.regalloc.RegisterPositions.Type;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -39,7 +38,6 @@
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntSet;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -139,8 +137,7 @@
   // List of intervals where the current instruction falls into one of their live range holes.
   private List<LiveIntervals> inactive = new LinkedList<>();
   // List of intervals that no register has been allocated to sorted by first live range.
-  private PriorityQueue<LiveIntervals> unhandled =
-      new PriorityQueue<>(Comparator.comparingInt(LiveIntervals::getStart));
+  private PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
 
   // The first register used for parallel moves. After register allocation the parallel move
   // temporary registers are [firstParallelMoveTemporary, maxRegisterNumber].
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 65628c3..0d8c6b8 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -15,7 +15,7 @@
 import java.util.List;
 import java.util.TreeSet;
 
-public class LiveIntervals {
+public class LiveIntervals implements Comparable<LiveIntervals> {
 
   private final Value value;
   private LiveIntervals nextConsecutive;
@@ -454,6 +454,12 @@
   }
 
   @Override
+  public int compareTo(LiveIntervals other) {
+    int startDiff = getStart() - other.getStart();
+    return startDiff != 0 ? startDiff : (value.getNumber() - other.value.getNumber());
+  }
+
+  @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     builder.append("(cons ");
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index 9cb873e..4a21e8a 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -10,7 +10,7 @@
 
 // Register moves used by the spilling register allocator. These are used both for spill and
 // for phi moves and they are moves between actual registers represented by their register number.
-public class RegisterMove {
+public class RegisterMove implements Comparable<RegisterMove> {
   MoveType type;
   int dst;
   int src;
@@ -70,4 +70,30 @@
     RegisterMove o = (RegisterMove) other;
     return o.src == src && o.dst == dst && o.type == type && o.definition == definition;
   }
+
+  @Override
+  public int compareTo(RegisterMove o) {
+    int srcDiff = src - o.src;
+    if (srcDiff != 0) {
+      return srcDiff;
+    }
+    int dstDiff = dst - o.dst;
+    if (dstDiff != 0) {
+      return dstDiff;
+    }
+    int typeDiff = o.type.ordinal() - type.ordinal();
+    if (typeDiff != 0) {
+      return typeDiff;
+    }
+    if (definition == null) {
+      if (o.definition != null) {
+        return -1;
+      }
+      return 0;
+    }
+    if (o.definition == null) {
+      return 1;
+    }
+    return definition.getNumber() - o.definition.getNumber();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index cc26729..007aa6f 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -14,15 +14,15 @@
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 public class RegisterMoveScheduler {
   // The set of moves to schedule.
-  private Set<RegisterMove> moveSet = new LinkedHashSet<>();
+  private Set<RegisterMove> moveSet = new TreeSet<>();
   // Mapping to keep track of which values currently corresponds to each other.
   // This is initially an identity map but changes as we insert moves.
   private Map<Integer, Integer> valueMap = new HashMap<>();
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 56613b3..5afcabb 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -308,6 +308,16 @@
     return "OK";
   }
 
+  public static void regression65066975(boolean bit) {
+    nop();
+    if (bit) {
+      nop();
+    } else {
+      nop();
+    }
+    nop();
+  }
+
   public static void main(String[] args) {
     noLocals();
     unusedLocals();
@@ -327,5 +337,6 @@
     switchRewriteToIfs(1);
     switchRewriteToSwitches(1);
     regression65039701(true);
+    regression65066975(false);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 926e84a..12fd23d 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -546,4 +546,23 @@
         checkLine(SOURCE_FILE, 308),
         run());
   }
+
+  @Test
+  public void regression65066975() throws Throwable {
+    runDebugTest(
+        "Locals",
+        breakpoint("Locals", "regression65066975"),
+        run(),
+        checkLine(SOURCE_FILE, 312),
+        checkLocal("bit", Value.createBoolean(false)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 313),
+        stepOver(),
+        checkLine(SOURCE_FILE, 316),
+        stepOver(),
+        checkLine(SOURCE_FILE, 318),
+        stepOver(),
+        checkLine(SOURCE_FILE, 319),
+        run());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
index be0a122..74992e9 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTest.java
@@ -19,8 +19,43 @@
     return -Math.abs(x);
   }
 
+  private static synchronized int throwing(int cond) {
+    int x = 42;
+    if (cond < 0) {
+      throw new IllegalStateException();
+    }
+    return 2;
+  }
+
+  private static synchronized int monitorExitRegression(int cond) {
+    int x = 42;
+    switch (cond) {
+      case 1:
+        return 1;
+      case 2:
+        throw new IllegalStateException();
+      case 3:
+        throw new RuntimeException();
+      case 4:
+        return 2;
+      case 5:
+        x = 7;
+      case 6:
+        return 3;
+      default:
+    }
+    if (cond > 0) {
+      x = cond + cond;
+    } else {
+      throw new ArithmeticException();
+    }
+    return 2;
+  }
+
   public static void main(String[] args) {
     System.out.println(syncStatic(1234));
     System.out.println(new SynchronizedMethodTest().syncInstance(1234));
+    System.out.println(throwing(1234));
+    System.out.println(monitorExitRegression(1234));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
index 7cee4a3..ba0849b 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
@@ -5,8 +5,9 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
 import org.junit.Test;
 
 public class SynchronizedMethodTestRunner extends DebugInfoTestBase {
@@ -18,7 +19,7 @@
     AndroidApp d8App = compileWithD8(clazz);
     AndroidApp dxApp = getDxCompiledSources();
 
-    String expected = "42" + ToolHelper.LINE_SEPARATOR + "42" + ToolHelper.LINE_SEPARATOR;
+    String expected = StringUtils.lines("42", "42", "2", "2");
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
     assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
@@ -28,6 +29,14 @@
 
     checkSyncInstance(inspectMethod(d8App, clazz, "int", "syncInstance", "int"));
     checkSyncInstance(inspectMethod(dxApp, clazz, "int", "syncInstance", "int"));
+
+    checkThrowing(inspectMethod(d8App, clazz, "int", "throwing", "int"), false);
+    checkThrowing(inspectMethod(dxApp, clazz, "int", "throwing", "int"), true);
+
+    checkMonitorExitRegression(
+        inspectMethod(d8App, clazz, "int", "monitorExitRegression", "int"), false);
+    checkMonitorExitRegression(
+        inspectMethod(dxApp, clazz, "int", "monitorExitRegression", "int"), true);
   }
 
   private void checkSyncStatic(DebugInfoInspector info) {
@@ -48,4 +57,25 @@
     info.checkLineHasExactLocals(19, locals);
     info.checkNoLine(20);
   }
+
+  private void checkThrowing(DebugInfoInspector info, boolean dx) {
+    info.checkStartLine(23);
+    if (!dx) {
+      info.checkLineHasExactLocals(23, "cond", "int");
+    }
+    info.checkLineHasExactLocals(24, "cond", "int", "x", "int");
+    info.checkLineHasExactLocals(25, "cond", "int", "x", "int");
+    info.checkNoLine(26);
+    info.checkLineHasExactLocals(27, "cond", "int", "x", "int");
+  }
+
+  private void checkMonitorExitRegression(DebugInfoInspector info, boolean dx) {
+    info.checkStartLine(31);
+    for (int line : Arrays.asList(32, 34, 36, 38, 40, 42, 44, 48, 50, 52)) {
+      if (dx && line == 40) {
+        continue;
+      }
+      info.checkLineHasExactLocals(line, "cond", "int", "x", "int");
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java
new file mode 100644
index 0000000..2cbd60a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8FrameworkDeterministicTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8FrameworkDeterministicTest extends CompilationTestBase {
+  private static final int MIN_SDK = 24;
+  private static final String JAR = "third_party/framework/framework_160115954.jar";
+
+  private AndroidApp doRun(D8Command command) throws IOException, CompilationException {
+    return ToolHelper.runD8(command);
+  }
+
+  @Test
+  public void verifyDebugBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    D8Command command = D8Command.builder()
+        .addProgramFiles(Paths.get(JAR))
+        .setMode(CompilationMode.DEBUG)
+        .setMinApiLevel(MIN_SDK)
+        .build();
+    AndroidApp app1 = doRun(command);
+    AndroidApp app2 = doRun(command);
+    assertIdenticalApplications(app1, app2);
+  }
+
+  @Test
+  public void verifyReleaseBuild()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    D8Command command = D8Command.builder()
+        .addProgramFiles(Paths.get(JAR))
+        .setMode(CompilationMode.RELEASE)
+        .setMinApiLevel(MIN_SDK)
+        .build();
+    AndroidApp app1 = doRun(command);
+    AndroidApp app2 = doRun(command);
+    assertIdenticalApplications(app1, app2);
+  }
+}