Fix nondeterminism in service loader rewriter

Bug: 156054499
Change-Id: Iaf322a99a7c79c904665c7cdfd0b0f705f08a355
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index bad82e1..14655d5 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -20,7 +20,7 @@
   private static final int ARRAY_BACKING_THRESHOLD = 30;
 
   private final DexClass holder;
-  private final MethodCollectionBacking backing;
+  private MethodCollectionBacking backing;
   private DexEncodedMethod cachedClassInitializer = DexEncodedMethod.SENTINEL;
 
   public MethodCollection(
@@ -291,6 +291,11 @@
         .shouldBreak();
   }
 
+  public void useSortedBacking() {
+    assert size() == 0;
+    backing = MethodMapBacking.createSorted();
+  }
+
   public boolean verify() {
     forEachMethod(
         method -> {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index 4ba116d..18257c1 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -10,7 +10,9 @@
 import com.google.common.base.Equivalence.Wrapper;
 import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceRBTreeMap;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Function;
@@ -21,7 +23,16 @@
   private Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> methodMap;
 
   public MethodMapBacking() {
-    this.methodMap = createMap();
+    this(createMap());
+  }
+
+  private MethodMapBacking(Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> methodMap) {
+    this.methodMap = methodMap;
+  }
+
+  public static MethodMapBacking createSorted() {
+    Comparator<Wrapper<DexMethod>> comparator = (x, y) -> x.get().slowCompareTo(y.get());
+    return new MethodMapBacking(new Object2ReferenceRBTreeMap<>(comparator));
   }
 
   private static Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> createMap() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 71ff0c2..2fb1a28 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1223,7 +1223,7 @@
     if (serviceLoaderRewriter != null) {
       assert appView.appInfo().hasLiveness();
       timing.begin("Rewrite service loaders");
-      serviceLoaderRewriter.rewrite(code);
+      serviceLoaderRewriter.rewrite(code, methodProcessingId);
       timing.end();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
index 60d970e..7e4397c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import java.util.function.BiConsumer;
 
@@ -58,6 +60,9 @@
       private final int firstReservedId;
       private final int numberOfReservedIds;
 
+      private final ProgramMethodSet seen =
+          InternalOptions.assertionsEnabled() ? ProgramMethodSet.create() : null;
+
       public ReservedMethodProcessingIds(int firstReservedId, int numberOfReservedIds) {
         this.firstReservedId = firstReservedId;
         this.numberOfReservedIds = numberOfReservedIds;
@@ -66,6 +71,7 @@
       public MethodProcessingId get(ProgramMethod method, int index) {
         assert index >= 0;
         assert index < numberOfReservedIds;
+        assert seen.add(method);
         MethodProcessingId result = new MethodProcessingId(firstReservedId + index);
         if (consumer != null) {
           consumer.accept(method, result);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 50666b6..d0ad73a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -29,11 +28,10 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -72,7 +70,6 @@
 
   public static final String SERVICE_LOADER_CLASS_NAME = "$$ServiceLoaderMethods";
   private static final String SERVICE_LOADER_METHOD_PREFIX_NAME = "$load";
-  private static final int SERVICE_LOADER_METHOD_HASH_LENGTH = 7;
 
   private AtomicReference<DexProgramClass> synthesizedClass = new AtomicReference<>();
   private ConcurrentHashMap<DexType, DexEncodedMethod> synthesizedServiceLoaders =
@@ -88,10 +85,9 @@
     return synthesizedClass.get();
   }
 
-  public void rewrite(IRCode code) {
+  public void rewrite(IRCode code, MethodProcessingId methodProcessingId) {
     DexItemFactory factory = appView.dexItemFactory();
     InstructionListIterator instructionIterator = code.instructionListIterator();
-    IntBox synthesizedLoadMethodCounter = new IntBox();
     while (instructionIterator.hasNext()) {
       Instruction instruction = instructionIterator.next();
 
@@ -176,8 +172,7 @@
               constClass.getValue(),
               service -> {
                 DexEncodedMethod addedMethod =
-                    createSynthesizedMethod(
-                        service, classes, code.context(), synthesizedLoadMethodCounter);
+                    createSynthesizedMethod(service, classes, methodProcessingId);
                 if (appView.options().isGeneratingClassFiles()) {
                   addedMethod.upgradeClassFileVersion(code.method().getClassFileVersion());
                 }
@@ -190,23 +185,9 @@
   }
 
   private DexEncodedMethod createSynthesizedMethod(
-      DexType serviceType,
-      List<DexClass> classes,
-      ProgramMethod context,
-      IntBox synthesizedLoadMethodCounter) {
+      DexType serviceType, List<DexClass> classes, MethodProcessingId methodProcessingId) {
     MethodCollection methodCollection = getOrSetSynthesizedClass().getMethodCollection();
-    String hashCode = Integer.toString(context.getReference().hashCode());
-    String methodNamePrefix =
-        SERVICE_LOADER_METHOD_PREFIX_NAME
-            + "$"
-            + StringUtils.replaceAll(context.getHolderType().toSourceString(), ".", "$")
-            + "$"
-            + (context.getDefinition().isInitializer()
-                ? (context.getDefinition().isClassInitializer() ? "$clinit" : "$init")
-                : context.getReference().name.toSourceString())
-            + "$"
-            + hashCode.substring(0, Math.min(SERVICE_LOADER_METHOD_HASH_LENGTH, hashCode.length()))
-            + "$";
+    String methodNamePrefix = SERVICE_LOADER_METHOD_PREFIX_NAME + "$";
     DexProto proto = appView.dexItemFactory().createProto(appView.dexItemFactory().iteratorType);
     synchronized (methodCollection) {
       DexMethod methodReference;
@@ -217,7 +198,7 @@
                 .createMethod(
                     appView.dexItemFactory().serviceLoaderRewrittenClassType,
                     proto,
-                    methodNamePrefix + "$" + synthesizedLoadMethodCounter.getAndIncrement());
+                    methodNamePrefix + methodProcessingId.getAndIncrementId());
       } while (methodCollection.getMethod(methodReference) != null);
       DexEncodedMethod method =
           new DexEncodedMethod(
@@ -241,30 +222,33 @@
     ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
     DexProgramClass clazz =
         synthesizedClass.updateAndGet(
-            existingClazz -> {
-              if (existingClazz != null) {
-                return existingClazz;
+            existingClass -> {
+              if (existingClass != null) {
+                return existingClass;
               }
-              return new DexProgramClass(
-                  appView.dexItemFactory().serviceLoaderRewrittenClassType,
-                  null,
-                  new SynthesizedOrigin("Service Loader desugaring", getClass()),
-                  ClassAccessFlags.fromDexAccessFlags(
-                      Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
-                  appView.dexItemFactory().objectType,
-                  DexTypeList.empty(),
-                  appView.dexItemFactory().createString("ServiceLoader"),
-                  null,
-                  Collections.emptyList(),
-                  null,
-                  Collections.emptyList(),
-                  DexAnnotationSet.empty(),
-                  DexEncodedField.EMPTY_ARRAY, // Static fields.
-                  DexEncodedField.EMPTY_ARRAY, // Instance fields.
-                  DexEncodedMethod.EMPTY_ARRAY,
-                  DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
-                  appView.dexItemFactory().getSkipNameValidationForTesting(),
-                  checksumSupplier);
+              DexProgramClass newClass =
+                  new DexProgramClass(
+                      appView.dexItemFactory().serviceLoaderRewrittenClassType,
+                      null,
+                      new SynthesizedOrigin("Service Loader desugaring", getClass()),
+                      ClassAccessFlags.fromDexAccessFlags(
+                          Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
+                      appView.dexItemFactory().objectType,
+                      DexTypeList.empty(),
+                      appView.dexItemFactory().createString("ServiceLoader"),
+                      null,
+                      Collections.emptyList(),
+                      null,
+                      Collections.emptyList(),
+                      DexAnnotationSet.empty(),
+                      DexEncodedField.EMPTY_ARRAY, // Static fields.
+                      DexEncodedField.EMPTY_ARRAY, // Instance fields.
+                      DexEncodedMethod.EMPTY_ARRAY,
+                      DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
+                      appView.dexItemFactory().getSkipNameValidationForTesting(),
+                      checksumSupplier);
+              newClass.getMethodCollection().useSortedBacking();
+              return newClass;
             });
     assert clazz != null;
     appView.appInfo().addSynthesizedClass(clazz);