Fix nondeterministic insertion of spill/restore moves

Bug: 197643889
Change-Id: Id6fea8e9025a3e698cc16a02ebbafabb7382289d
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 0447c1a..e5c792f 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
@@ -27,6 +27,7 @@
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.LinkedHashSetUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -61,7 +62,7 @@
 
   public static class LiveAtEntrySets {
     // Set of live SSA values (regardless of whether they denote a local variable).
-    public final Set<Value> liveValues;
+    public final LinkedHashSet<Value> liveValues;
 
     // Subset of live local-variable values.
     public final Set<Value> liveLocalValues;
@@ -69,7 +70,7 @@
     public final Deque<Value> liveStackValues;
 
     public LiveAtEntrySets(
-        Set<Value> liveValues, Set<Value> liveLocalValues, Deque<Value> liveStackValues) {
+        LinkedHashSet<Value> liveValues, Set<Value> liveLocalValues, Deque<Value> liveStackValues) {
       assert liveValues.containsAll(liveLocalValues);
       this.liveValues = liveValues;
       this.liveLocalValues = liveLocalValues;
@@ -176,14 +177,14 @@
     while (!worklist.isEmpty()) {
       BasicBlock block = worklist.poll();
       // Note that the iteration order of live values matters when inserting spill/restore moves.
-      Set<Value> live = new LinkedHashSet<>();
+      LinkedHashSet<Value> live = new LinkedHashSet<>();
       Set<Value> liveLocals = Sets.newIdentityHashSet();
       Deque<Value> liveStack = new ArrayDeque<>();
       Set<BasicBlock> exceptionalSuccessors = block.getCatchHandlers().getUniqueTargets();
       for (BasicBlock succ : block.getSuccessors()) {
         LiveAtEntrySets liveAtSucc = liveAtEntrySets.get(succ);
         if (liveAtSucc != null) {
-          live.addAll(liveAtSucc.liveValues);
+          LinkedHashSetUtils.addAll(live, liveAtSucc.liveValues);
           liveLocals.addAll(liveAtSucc.liveLocalValues);
           // The stack is only allowed to be non-empty in the case of linear-flow (so-far).
           // If succ is an exceptional successor the successor stack should be empty
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 e98adec..537d9af 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
@@ -38,6 +38,7 @@
 import com.android.tools.r8.ir.regalloc.RegisterPositions.Type;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.LinkedHashSetUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.HashMultiset;
@@ -60,6 +61,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -2524,9 +2526,10 @@
       Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets,
       List<LiveIntervals> liveIntervals) {
     for (BasicBlock block : code.topologicallySortedBlocks()) {
-      Set<Value> live = Sets.newIdentityHashSet();
-      Set<Value> phiOperands = Sets.newIdentityHashSet();
-      Set<Value> liveAtThrowingInstruction = Sets.newIdentityHashSet();
+      // Linked collections to ensure determinism of liveIntervals (see also b/197643889).
+      LinkedHashSet<Value> live = Sets.newLinkedHashSet();
+      LinkedHashSet<Value> phiOperands = Sets.newLinkedHashSet();
+      LinkedHashSet<Value> liveAtThrowingInstruction = Sets.newLinkedHashSet();
       Set<BasicBlock> exceptionalSuccessors = block.getCatchHandlers().getUniqueTargets();
       for (BasicBlock successor : block.getSuccessors()) {
         // Values live at entry to a block that is an exceptional successor are only live
@@ -2534,9 +2537,10 @@
         // the block only if they are used in normal flow as well.
         boolean isExceptionalSuccessor = exceptionalSuccessors.contains(successor);
         if (isExceptionalSuccessor) {
-          liveAtThrowingInstruction.addAll(liveAtEntrySets.get(successor).liveValues);
+          LinkedHashSetUtils.addAll(
+              liveAtThrowingInstruction, liveAtEntrySets.get(successor).liveValues);
         } else {
-          live.addAll(liveAtEntrySets.get(successor).liveValues);
+          LinkedHashSetUtils.addAll(live, liveAtEntrySets.get(successor).liveValues);
         }
         // Exception blocks should not have any phis (if an exception block has more than one
         // predecessor, then we insert a split block in-between).
@@ -2546,7 +2550,7 @@
           phiOperands.add(phi.getOperand(successor.getPredecessors().indexOf(block)));
         }
       }
-      live.addAll(phiOperands);
+      LinkedHashSetUtils.addAll(live, phiOperands);
       List<Instruction> instructions = block.getInstructions();
       for (Value value : live) {
         int end = block.entry().getNumber() + instructions.size() * INSTRUCTION_NUMBER_DELTA;
@@ -2635,7 +2639,9 @@
           // In debug mode, or if the method is reachability sensitive, extend the live range
           // to cover the full scope of a local variable (encoded as debug values).
           int number = instruction.getNumber();
-          for (Value use : instruction.getDebugValues()) {
+          List<Value> sortedDebugValues = new ArrayList<>(instruction.getDebugValues());
+          sortedDebugValues.sort(Value::compareTo);
+          for (Value use : sortedDebugValues) {
             assert use.needsRegister();
             if (!live.contains(use)) {
               live.add(use);
diff --git a/src/main/java/com/android/tools/r8/utils/LinkedHashSetUtils.java b/src/main/java/com/android/tools/r8/utils/LinkedHashSetUtils.java
new file mode 100644
index 0000000..d7812fb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/LinkedHashSetUtils.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, 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.utils;
+
+import java.util.LinkedHashSet;
+
+public class LinkedHashSetUtils {
+
+  public static <T> void addAll(LinkedHashSet<T> set, LinkedHashSet<T> elements) {
+    set.addAll(elements);
+  }
+}