diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index baf36e21a2..b088626a73 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -1023,7 +1023,8 @@ public JMethod getStaticImpl(JMethod method) {
return staticImpl;
}
- public JArrayType getTypeArray(JType elementType) {
+ // Synchronized because CodeSplitter reaches this from concurrent CFA traversals.
+ public synchronized JArrayType getTypeArray(JType elementType) {
JArrayType arrayType = arrayTypes.get(elementType);
if (arrayType == null) {
arrayType = new JArrayType(elementType);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/CodeSplitter.java
index 540d6a58c7..fbf1d8df79 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/CodeSplitter.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/CodeSplitter.java
@@ -43,6 +43,11 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
*
@@ -274,6 +279,12 @@ private List statementsForFragment(int fragmentId,
*/
private Map computeNotExclusiveCfaForFragments(
Collection exclusiveFragments) {
+ // Recording a dependency graph is order sensitive, so it keeps the serial loop below.
+ if (dependencyRecorder == MultipleDependencyGraphRecorder.NULL_RECORDER
+ && exclusiveFragments.size() > 1) {
+ return computeNotExclusiveCfaForFragmentsInParallel(exclusiveFragments);
+ }
+
String dependencyGraphNameAfterInitialSequence = dependencyGraphNameAfterInitialSequence();
Map notExclusiveCfaByFragment = Maps.newHashMap();
@@ -304,6 +315,59 @@ private Map computeNotExclusiveCfaForFragments(
return notExclusiveCfaByFragment;
}
+ /**
+ * Runs {@link #computeNotExclusiveCfaForFragment} for every fragment in parallel. The iterations
+ * are independent: each builds its own ControlFlowAnalyzer, whose copy constructor duplicates
+ * every mutable set, and otherwise only reads the program.
+ */
+ private Map computeNotExclusiveCfaForFragmentsInParallel(
+ final Collection exclusiveFragments) {
+ Map notExclusiveCfaByFragment = Maps.newLinkedHashMap();
+ int threads = Math.min(Runtime.getRuntime().availableProcessors(), exclusiveFragments.size());
+ ExecutorService executor = Executors.newFixedThreadPool(threads);
+ try {
+ Map> futures = Maps.newLinkedHashMap();
+ for (final Fragment fragment : exclusiveFragments) {
+ assert fragment.isExclusive();
+ futures.put(fragment, executor.submit(new Callable() {
+ @Override
+ public ControlFlowAnalyzer call() {
+ return computeNotExclusiveCfaForFragment(fragment, exclusiveFragments);
+ }
+ }));
+ }
+ for (Map.Entry> entry : futures.entrySet()) {
+ try {
+ notExclusiveCfaByFragment.put(entry.getKey(), entry.getValue().get());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+ } finally {
+ executor.shutdown();
+ }
+ return notExclusiveCfaByFragment;
+ }
+
+ private ControlFlowAnalyzer computeNotExclusiveCfaForFragment(Fragment fragment,
+ Collection exclusiveFragments) {
+ ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(initialSequenceCfa);
+ for (Fragment otherFragment : exclusiveFragments) {
+ // don't trace the initial fragments as they have already been traced and their atoms are
+ // already in {@code initialSequenceCfa}.
+ if (otherFragment.isInitial() || otherFragment == fragment) {
+ continue;
+ }
+ for (JRunAsync otherRunAsync : otherFragment.getRunAsyncs()) {
+ cfa.traverseFromRunAsync(otherRunAsync);
+ }
+ }
+ return cfa;
+ }
+
/**
* Compute a CFA that covers the entire live code of the program.
*/