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. */