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();
}