Use linked sets for keeping order of StringBuilderNodes

Bug: b/236948782
Change-Id: I269c85b92922fae67f746c21597a6e4b05aea531
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 702cf76..830516e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -44,6 +44,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -155,7 +156,7 @@
               DFSNodeWithState<BasicBlock, StringBuilderGraphState> node,
               Function<BasicBlock, DFSNodeWithState<BasicBlock, StringBuilderGraphState>>
                   childNodeConsumer) {
-            Map<Value, StringBuilderNode> currentRoots = new IdentityHashMap<>();
+            Map<Value, StringBuilderNode> currentRoots = new LinkedHashMap<>();
             Map<Value, StringBuilderNode> currentTails = new IdentityHashMap<>();
             BasicBlock block = node.getNode();
             StringBuilderEscapeState previousState = analysis.computeBlockEntryState(block);
@@ -434,11 +435,12 @@
               }
             }
             if (state.isPartOfLoop) {
-              for (Value value : state.roots.keySet()) {
-                LoopNode loopNode = StringBuilderNode.createLoopNode();
-                loopNode.addSuccessor(state.roots.get(value));
-                state.roots.put(value, loopNode);
-              }
+              state.roots.replaceAll(
+                  (value, currentRoot) -> {
+                    LoopNode loopNode = StringBuilderNode.createLoopNode();
+                    loopNode.addSuccessor(currentRoot);
+                    return loopNode;
+                  });
             }
             return TraversalContinuation.doContinue(state);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
index acd5c47..22d5c70 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
@@ -39,8 +39,8 @@
     ImplicitToStringNode getImplicitToStringNode();
   }
 
-  private final Set<StringBuilderNode> successors = Sets.newIdentityHashSet();
-  private final Set<StringBuilderNode> predecessors = Sets.newIdentityHashSet();
+  private final Set<StringBuilderNode> successors = Sets.newLinkedHashSet();
+  private final Set<StringBuilderNode> predecessors = Sets.newLinkedHashSet();
 
   // Field uses to ensure that munching will not operate on the same value multiple times. If all
   // peep holes would look in the same direction, this field could be removed.