Version 1.3.29

Merge: Reprocess methods concurrently in class staticizer
CL: https://r8-review.googlesource.com/c/r8/+/29425

Bug: 117906096
Change-Id: I2c7394e5b3b51fc2cb96d9088e8e7b9ea8ab05c6
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 812fec2..7c7e830 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.28";
+  public static final String LABEL = "1.3.29";
 
   private Version() {
   }
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 00d8bdf..b9818ad 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
@@ -291,9 +291,10 @@
     }
   }
 
-  private void staticizeClasses(OptimizationFeedback feedback) {
+  private void staticizeClasses(OptimizationFeedback feedback, ExecutorService executorService)
+      throws ExecutionException {
     if (classStaticizer != null) {
-      classStaticizer.staticizeCandidates(feedback);
+      classStaticizer.staticizeCandidates(feedback, executorService);
     }
   }
 
@@ -494,9 +495,10 @@
     // Build a new application with jumbo string info.
     Builder<?> builder = application.builder();
     builder.setHighestSortingString(highestSortingString);
-    // b/112831361
+
+    // TODO(b/112831361): Implement support for staticizeClasses in CF backend.
     if (!options.isGeneratingClassFiles()) {
-      staticizeClasses(directFeedback);
+      staticizeClasses(directFeedback, executorService);
     }
 
     // Second inlining pass for dealing with double inline callers.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index e33d977..711f63a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -35,6 +35,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -526,9 +528,10 @@
   //  2. Rewrite instance methods of classes being staticized into static ones
   //  3. Rewrite methods referencing staticized members, also remove instance creation
   //
-  public final void staticizeCandidates(OptimizationFeedback feedback) {
+  public final void staticizeCandidates(
+      OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
     phase = Phase.None; // We are done with processing/examining methods.
-    new StaticizingProcessor(this).run(feedback);
+    new StaticizingProcessor(this, executorService).run(feedback);
   }
 
   public final void fixupMethodCode(DexEncodedMethod method, IRCode code) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index b7c830f..18a9920 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Sets;
@@ -40,12 +41,17 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 final class StaticizingProcessor {
+
   private final ClassStaticizer classStaticizer;
+  private final ExecutorService executorService;
 
   private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
   private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
@@ -53,35 +59,36 @@
   private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
   private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
 
-  StaticizingProcessor(ClassStaticizer classStaticizer) {
+  StaticizingProcessor(ClassStaticizer classStaticizer, ExecutorService executorService) {
     this.classStaticizer = classStaticizer;
+    this.executorService = executorService;
   }
 
-  final void run(OptimizationFeedback feedback) {
-    // Filter out candidates based on the information we collected
-    // while examining methods.
+  final void run(OptimizationFeedback feedback) throws ExecutionException {
+    // Filter out candidates based on the information we collected while examining methods.
     finalEligibilityCheck();
 
     // Prepare interim data.
     prepareCandidates();
 
     // Process all host class initializers (only remove instantiations).
-    processMethods(hostClassInits.keySet().stream(), this::removeCandidateInstantiation, feedback);
+    processMethodsConcurrently(
+        hostClassInits.keySet(), this::removeCandidateInstantiation, feedback);
 
     // Process instance methods to be staticized (only remove references to 'this').
-    processMethods(methodsToBeStaticized.stream(), this::removeReferencesToThis, feedback);
+    processMethodsConcurrently(methodsToBeStaticized, this::removeReferencesToThis, feedback);
 
     // Convert instance methods into static methods with an extra parameter.
     Set<DexEncodedMethod> staticizedMethods = staticizeMethodSymbols();
 
-    // Process all other methods that may reference singleton fields
-    // and call methods on them. (Note that we exclude the former instance methods,
-    // but include new static methods created as a result of staticizing.
-    Stream<DexEncodedMethod> methods = Streams.concat(
-        referencingExtraMethods.stream(),
-        staticizedMethods.stream(),
-        hostClassInits.keySet().stream());
-    processMethods(methods, this::rewriteReferences, feedback);
+    // Process all other methods that may reference singleton fields and call methods on them.
+    // (Note that we exclude the former instance methods, but include new static methods created as
+    // a result of staticizing.)
+    Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
+    methods.addAll(referencingExtraMethods);
+    methods.addAll(staticizedMethods);
+    methods.addAll(hostClassInits.keySet());
+    processMethodsConcurrently(methods, this::rewriteReferences, feedback);
   }
 
   private void finalEligibilityCheck() {
@@ -165,12 +172,39 @@
     referencingExtraMethods.removeAll(removedInstanceMethods);
   }
 
-  private void processMethods(Stream<DexEncodedMethod> methods,
-      BiConsumer<DexEncodedMethod, IRCode> strategy, OptimizationFeedback feedback) {
+  /**
+   * Processes the given methods concurrently using the given strategy.
+   *
+   * <p>Note that, when the strategy {@link #rewriteReferences(DexEncodedMethod, IRCode)} is being
+   * applied, it is important that we never inline a method from `methods` which has still not been
+   * reprocessed. This could lead to broken code, because the strategy that rewrites the broken
+   * references is applied *before* inlining (because the broken references in the inlinee are never
+   * rewritten). We currently avoid this situation by processing all the methods concurrently
+   * (inlining of a method that is processed concurrently is not allowed).
+   */
+  private void processMethodsConcurrently(
+      Set<DexEncodedMethod> methods,
+      BiConsumer<DexEncodedMethod, IRCode> strategy,
+      OptimizationFeedback feedback)
+      throws ExecutionException {
     classStaticizer.setFixupStrategy(strategy);
-    methods.sorted(DexEncodedMethod::slowCompare).forEach(
-        method -> classStaticizer.converter.processMethod(method, feedback,
-            x -> false, CallSiteInformation.empty(), Outliner::noProcessing));
+
+    List<Future<?>> futures = new ArrayList<>();
+    for (DexEncodedMethod method : methods) {
+      futures.add(
+          executorService.submit(
+              () -> {
+                classStaticizer.converter.processMethod(
+                    method,
+                    feedback,
+                    methods::contains,
+                    CallSiteInformation.empty(),
+                    Outliner::noProcessing);
+                return null; // we want a Callable not a Runnable to be able to throw
+              }));
+    }
+    ThreadUtils.awaitFutures(futures);
+
     classStaticizer.cleanFixupStrategy();
   }