Parallelize parsing of cf code in tree shaking

Bug: b/402328454
Change-Id: I3d6ff5872aa45808a2e2b61c9983c25884aaa89f
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 7d7227d..4b513ce 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -232,7 +232,7 @@
             clazz -> {
               ProgramMethod classInitializer = clazz.getProgramClassInitializer();
               if (classInitializer != null) {
-                analysis.processNewlyLiveMethod(classInitializer, clazz, null, null);
+                analysis.processNewlyLiveCode(classInitializer, null, null);
               }
             },
             appView.options().getThreadingModule(),
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index 7362ef4..5f99150 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -23,11 +23,11 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
-import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
@@ -37,7 +37,7 @@
 import org.objectweb.asm.Opcodes;
 
 public class ClassInitializerAssertionEnablingAnalysis
-    implements TraceFieldAccessEnqueuerAnalysis, NewlyLiveMethodEnqueuerAnalysis {
+    implements TraceFieldAccessEnqueuerAnalysis, NewlyLiveCodeEnqueuerAnalysis {
   private final DexItemFactory dexItemFactory;
   private final OptimizationFeedback feedback;
   private final DexString kotlinAssertionsEnabled;
@@ -65,7 +65,7 @@
       ClassInitializerAssertionEnablingAnalysis analysis =
           new ClassInitializerAssertionEnablingAnalysis(
               appView, OptimizationFeedbackSimple.getInstance());
-      builder.addTraceFieldAccessAnalysis(analysis).addNewlyLiveMethodAnalysis(analysis);
+      builder.addTraceFieldAccessAnalysis(analysis).addNewlyLiveCodeAnalysis(analysis);
     }
   }
 
@@ -94,11 +94,8 @@
   }
 
   @Override
-  public void processNewlyLiveMethod(
-      ProgramMethod method,
-      ProgramDefinition context,
-      Enqueuer enqueuer,
-      EnqueuerWorklist worklist) {
+  public void processNewlyLiveCode(
+      ProgramMethod method, DefaultEnqueuerUseRegistry registry, EnqueuerWorklist worklist) {
     DexEncodedMethod definition = method.getDefinition();
     if (!definition.hasCode() || !definition.getCode().isCfCode()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 0637491..fcb3ed2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -460,7 +460,7 @@
   private final GraphReporter graphReporter;
 
   private final CfInstructionDesugaringCollection desugaring;
-  private final ProgramMethodSet pendingCodeDesugaring = ProgramMethodSet.create();
+  private final ProgramMethodSet pendingCodeDesugaring = ProgramMethodSet.createConcurrent();
 
   // Collections for tracing progress on interface method desugaring.
 
@@ -521,7 +521,8 @@
     this.options = options;
     this.keepInfo = new MutableKeepInfoCollection(options);
     this.useRegistryFactory = createUseRegistryFactory();
-    this.worklist = EnqueuerWorklist.createWorklist(this);
+    this.worklist =
+        EnqueuerWorklist.createWorklist(this, executorService, options.getThreadingModule());
     this.proguardCompatibilityActionsBuilder =
         mode.isInitialTreeShaking() && options.forceProguardCompatibility
             ? ProguardCompatibilityActions.builder()
@@ -4229,6 +4230,9 @@
     if (!method.getDefinition().hasCode() || method.getDefinition().getCode().isDexCode()) {
       return false;
     }
+    // TODO(b/294886627): This no longer includes the time to parse and check needs desugaring.
+    //  Consider timing this on the thread and merging it into the main timing when awaiting
+    //  futures.
     try (Timing t0 = timing.begin("Analyze needs desugaring")) {
       try (Timing t1 = timing.begin("Analyze interface method desugaring")) {
         if (options.isInterfaceMethodDesugaringEnabled()) {
@@ -4255,16 +4259,26 @@
         assert !desugaring.needsDesugaring(method);
         return false;
       }
-      // TODO(b/402328454): Parallelize parsing of LazyCfcode.
-      if (desugaring.needsDesugaring(method)) {
-        pendingCodeDesugaring.add(method);
-        return true;
-      }
-      return false;
+      worklist.enqueueFuture(() -> parseCodeAndCheckNeedsDesugaring(method));
+      return true;
+    }
+  }
+
+  private void parseCodeAndCheckNeedsDesugaring(ProgramMethod method) {
+    LazyCfCode code = method.getDefinition().getCode().asLazyCfCode();
+    if (code != null) {
+      code.parseCodeConcurrently();
+    }
+    if (desugaring.needsDesugaring(method)) {
+      pendingCodeDesugaring.add(method);
+    } else {
+      worklist.enqueueTraceCodeAction(method);
     }
   }
 
   private void desugar(SyntheticAdditions additions) throws ExecutionException {
+    assert worklist.verifyNotProcessing();
+
     if (pendingCodeDesugaring.isEmpty() && pendingMethodMove.isEmpty()) {
       return;
     }
@@ -4714,10 +4728,7 @@
         long numberOfLiveItems = getNumberOfLiveItems();
 
         timing.begin("Process worklist");
-        while (worklist.hasNext()) {
-          EnqueuerAction action = worklist.poll();
-          action.run(this, timing);
-        }
+        worklist.process(timing);
         timing.end();
 
         // Continue fix-point processing if -if rules are enabled by items that newly became live.
@@ -4813,6 +4824,8 @@
     } finally {
       timing.end();
     }
+
+    assert worklist.verifyNoPendingFutures();
   }
 
   private void postProcessingDesugaring(Timing timing) throws ExecutionException {
@@ -4857,12 +4870,10 @@
 
     syntheticAdditions.enqueueWorkItems(this);
 
+    timing.begin("Process worklist");
     worklist = worklist.nonPushable();
-
-    while (worklist.hasNext()) {
-      EnqueuerAction action = worklist.poll();
-      action.run(this, Timing.empty());
-    }
+    worklist.process(timing);
+    timing.end();
   }
 
   public long getNumberOfLiveItems() {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index baf0650..eda1979 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -18,13 +18,20 @@
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
+import com.android.tools.r8.threading.ThreadingModule;
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.UncheckedExecutionException;
 import com.android.tools.r8.utils.timing.Timing;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Objects;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 public abstract class EnqueuerWorklist {
 
@@ -561,15 +568,48 @@
   }
 
   final Enqueuer enqueuer;
+  final List<Future<Void>> futures = new ArrayList<>();
   final Queue<EnqueuerAction> queue;
+  final ThreadingModule threadingModule;
 
-  public static EnqueuerWorklist createWorklist(Enqueuer enqueuer) {
-    return new PushableEnqueuerWorkList(enqueuer);
+  boolean processing;
+
+  public static EnqueuerWorklist createWorklist(
+      Enqueuer enqueuer, ExecutorService executorService, ThreadingModule threadingModule) {
+    return new PushableEnqueuerWorkList(enqueuer, executorService, threadingModule);
   }
 
-  private EnqueuerWorklist(Enqueuer enqueuer, Queue<EnqueuerAction> queue) {
+  private EnqueuerWorklist(
+      Enqueuer enqueuer, Queue<EnqueuerAction> queue, ThreadingModule threadingModule) {
     this.enqueuer = enqueuer;
     this.queue = queue;
+    this.threadingModule = threadingModule;
+  }
+
+  void process(Timing timing) throws ExecutionException {
+    processing = true;
+    while (hasNext()) {
+      while (hasNext()) {
+        EnqueuerAction action = poll();
+        action.run(enqueuer, timing);
+      }
+      timing.begin("Await futures");
+      threadingModule.awaitFutures(futures);
+      futures.clear();
+      timing.end();
+    }
+    processing = false;
+    assert verifyNoPendingFutures();
+  }
+
+  boolean verifyNoPendingFutures() {
+    assert futures.isEmpty();
+    return true;
+  }
+
+  boolean verifyNotProcessing() {
+    assert !processing;
+    return true;
   }
 
   public boolean hasNext() {
@@ -581,6 +621,7 @@
   }
 
   public EnqueuerAction poll() {
+    assert processing;
     return queue.poll();
   }
 
@@ -594,6 +635,8 @@
 
   abstract boolean enqueueAssertAction(Action assertion);
 
+  abstract void enqueueFuture(Action action);
+
   abstract void enqueueMarkReachableDirectAction(
       DexMethod method, ProgramDefinition context, KeepReason reason);
 
@@ -654,8 +697,12 @@
 
   static class PushableEnqueuerWorkList extends EnqueuerWorklist {
 
-    PushableEnqueuerWorkList(Enqueuer enqueuer) {
-      super(enqueuer, new ConcurrentLinkedQueue<>());
+    private final ExecutorService executorService;
+
+    PushableEnqueuerWorkList(
+        Enqueuer enqueuer, ExecutorService executorService, ThreadingModule threadingModule) {
+      super(enqueuer, new ConcurrentLinkedQueue<>(), threadingModule);
+      this.executorService = executorService;
     }
 
     @Override
@@ -677,6 +724,17 @@
     }
 
     @Override
+    void enqueueFuture(Action action) {
+      // We currently only enqueue single threaded and thus do not need synchronization here.
+      assert processing;
+      try {
+        futures.add(threadingModule.submit(action, executorService));
+      } catch (ExecutionException e) {
+        throw new UncheckedExecutionException(e);
+      }
+    }
+
+    @Override
     void enqueueMarkReachableDirectAction(
         DexMethod method, ProgramDefinition context, KeepReason reason) {
       queue.add(new MarkReachableDirectAction(method, context, reason));
@@ -833,7 +891,7 @@
   public static class NonPushableEnqueuerWorklist extends EnqueuerWorklist {
 
     private NonPushableEnqueuerWorklist(PushableEnqueuerWorkList workList) {
-      super(workList.enqueuer, workList.queue);
+      super(workList.enqueuer, workList.queue, workList.threadingModule);
     }
 
     @Override
@@ -858,6 +916,11 @@
     }
 
     @Override
+    void enqueueFuture(Action action) {
+      throw new Unreachable("Attempt to enqueue a future in a non pushable enqueuer work list");
+    }
+
+    @Override
     void enqueueMarkReachableDirectAction(
         DexMethod method, ProgramDefinition context, KeepReason reason) {
       throw attemptToEnqueue("MarkReachableDirectAction " + method + " from " + context);
diff --git a/src/main/java/com/android/tools/r8/utils/Action.java b/src/main/java/com/android/tools/r8/utils/Action.java
index 5ab0177..321ee28 100644
--- a/src/main/java/com/android/tools/r8/utils/Action.java
+++ b/src/main/java/com/android/tools/r8/utils/Action.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.utils;
 
+import java.util.concurrent.Callable;
+
 @FunctionalInterface
-public interface Action {
+public interface Action extends Callable<Void> {
 
   Action EMPTY = () -> {};
 
@@ -13,5 +15,11 @@
     return EMPTY;
   }
 
+  @Override
+  default Void call() throws Exception {
+    execute();
+    return null;
+  }
+
   void execute();
 }