Allow specifying an initial load sequence for runAsync calls
in an application's gwt.xml file. If the specified sequence
matches the actual order the runAsync's are reached at
run time, then the load time is improved because the leftovers
fragment can be loaded later than otherwise. If the load
order is incorrect, the program still runs, but it
will likely be slowed down.
Review by: bobv
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5560 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java
index 30b9781..1062194 100644
--- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java
@@ -16,8 +16,11 @@
package com.google.gwt.core.ext.soyc.impl;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement;
import com.google.gwt.dev.util.HtmlTextOutput;
+import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.util.tools.Utility;
import java.io.OutputStream;
@@ -30,18 +33,13 @@
* Records split points to a file for SOYC reports.
*/
public class SplitPointRecorder {
-
/**
* Used to record (runAsync) split points of a program.
- *
- * @param jprogram
- * @param out
- * @param logger
*/
public static void recordSplitPoints(JProgram jprogram, OutputStream out,
TreeLogger logger) {
- logger = logger.branch(TreeLogger.INFO,
+ logger = logger.branch(TreeLogger.TRACE,
"Creating Split Point Map file for SOYC");
try {
@@ -59,19 +57,22 @@
htmlOut.indentIn();
htmlOut.indentIn();
- Map<Integer, String> splitPointMap = jprogram.getSplitPointMap();
+ Map<Integer, String> splitPointMap = splitPointNames(jprogram);
if (splitPointMap.size() > 0) {
curLine = "<splitpoints>";
htmlOut.printRaw(curLine);
htmlOut.newline();
htmlOut.indentIn();
htmlOut.indentIn();
- for (Map.Entry<Integer, String> entry : splitPointMap.entrySet()) {
- Integer splitPointCount = entry.getKey();
- curLine = "<splitpoint id=\"" + splitPointCount + "\" location=\""
- + entry.getValue() + "\"/>";
+ for (int sp = 1; sp <= splitPointMap.size(); sp++) {
+ String location = splitPointMap.get(sp);
+ assert location != null;
+ curLine = "<splitpoint id=\"" + sp + "\" location=\"" + location
+ + "\"/>";
htmlOut.printRaw(curLine);
htmlOut.newline();
+ logger.log(TreeLogger.TRACE, "Assigning split point #" + sp
+ + " in method " + location);
}
htmlOut.indentOut();
htmlOut.indentOut();
@@ -96,6 +97,33 @@
}
}
+ private static String fullMethodDescription(JMethod method) {
+ return (method.getEnclosingType().getName() + "." + JProgram.getJsniSig(method));
+ }
+
+ /**
+ * Choose human-readable names for the split points.
+ */
+ private static Map<Integer, String> splitPointNames(JProgram program) {
+ Map<Integer, String> names = new HashMap<Integer, String>();
+ Map<String, Integer> counts = new HashMap<String, Integer>();
+ for (RunAsyncReplacement replacement : program.getRunAsyncReplacements().values()) {
+ int entryNumber = replacement.getNumber();
+ String methodDescription = fullMethodDescription(replacement.getEnclosingMethod());
+ if (counts.containsKey(methodDescription)) {
+ counts.put(methodDescription, counts.get(methodDescription) + 1);
+ methodDescription += "#"
+ + Integer.toString(counts.get(methodDescription));
+ } else {
+ counts.put(methodDescription, 1);
+ }
+
+ names.put(entryNumber, methodDescription);
+ }
+
+ return names;
+ }
+
private SplitPointRecorder() {
}
}
diff --git a/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java b/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
index f208795..2d93ab4 100644
--- a/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java
@@ -40,8 +40,12 @@
ArtifactSet artifacts) throws UnableToCompleteException {
ArtifactSet results = new ArtifactSet(artifacts);
for (StandardCompilationAnalysis soycFiles : artifacts.find(StandardCompilationAnalysis.class)) {
- results.add(soycFiles.getDepFile());
- results.add(soycFiles.getStoriesFile());
+ if (soycFiles.getDepFile() != null) {
+ results.add(soycFiles.getDepFile());
+ }
+ if (soycFiles.getStoriesFile() != null) {
+ results.add(soycFiles.getStoriesFile());
+ }
results.add(soycFiles.getSplitPointsFile());
}
return results;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 282e4a1..4c9282a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -120,7 +120,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -128,8 +127,8 @@
import java.util.TreeSet;
/**
- * Compiles the Java <code>JProgram</code> representation into its
- * corresponding JavaScript source.
+ * Compiles the Java <code>JProgram</code> representation into its corresponding
+ * JavaScript source.
*/
public class JavaToJavaScriptCompiler {
@@ -182,14 +181,16 @@
* {@link #precompile(TreeLogger, WebModeCompilerFrontEnd, String[], JJSOptions, boolean)}
* @param rebindAnswers the set of rebind answers to resolve all outstanding
* rebind decisions
- * @param propertyOracles All property oracles corresponding to this permutation.
+ * @param propertyOracles All property oracles corresponding to this
+ * permutation.
* @return the output JavaScript
* @throws UnableToCompleteException if an error other than
* {@link OutOfMemoryError} occurs
*/
public static PermutationResult compilePermutation(TreeLogger logger,
UnifiedAst unifiedAst, Map<String, String> rebindAnswers,
- PropertyOracle[] propertyOracles, int permutationId) throws UnableToCompleteException {
+ PropertyOracle[] propertyOracles, int permutationId)
+ throws UnableToCompleteException {
long permStart = System.currentTimeMillis();
try {
if (JProgram.isTracingEnabled()) {
@@ -214,13 +215,6 @@
optimize(options, jprogram);
}
- // (4.5) Choose an initial load order sequence for runAsync's
- LinkedHashSet<Integer> initialLoadSequence = new LinkedHashSet<Integer>();
- if (options.isAggressivelyOptimize() && options.isRunAsyncEnabled()) {
- initialLoadSequence = CodeSplitter.pickInitialLoadSequence(logger,
- jprogram);
- }
-
// (5) "Normalize" the high-level Java tree into a lower-level tree more
// suited for JavaScript code generation. Don't go reordering these
// willy-nilly because there are some subtle interdependencies.
@@ -293,8 +287,7 @@
// (10.5) Split up the program into fragments
if (options.isAggressivelyOptimize() && options.isRunAsyncEnabled()) {
- CodeSplitter.exec(logger, jprogram, jsProgram, postStringInterningMap,
- initialLoadSequence);
+ CodeSplitter.exec(logger, jprogram, jsProgram, postStringInterningMap);
}
// (11) Perform any post-obfuscation normalizations.
@@ -331,45 +324,8 @@
PermutationResult toReturn = new PermutationResultImpl(js,
makeSymbolMap(symbolTable), ranges);
- if (sourceInfoMaps != null) {
- long soycStart = System.currentTimeMillis();
- System.out.println("Computing SOYC output");
- // Free up memory.
- symbolTable = null;
-
- long start = System.currentTimeMillis();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- // get method dependencies
- StoryRecorder.recordStories(logger, baos, sourceInfoMaps, js);
- SoycArtifact stories = new SoycArtifact("stories" + permutationId
- + ".xml.gz", baos.toByteArray());
- // Free up memory.
- js = null;
- System.out.println("Record stories: "
- + (System.currentTimeMillis() - start) + " ms");
-
- start = System.currentTimeMillis();
- baos.reset();
- DependencyRecorder.recordDependencies(logger, baos, jprogram);
- SoycArtifact dependencies = new SoycArtifact("dependencies"
- + permutationId + ".xml.gz", baos.toByteArray());
- System.out.println("Record dependencies: "
- + (System.currentTimeMillis() - start) + " ms");
-
- start = System.currentTimeMillis();
- baos.reset();
- SplitPointRecorder.recordSplitPoints(jprogram, baos, logger);
- SoycArtifact splitPoints = new SoycArtifact("splitPoints"
- + permutationId + ".xml.gz", baos.toByteArray());
- System.out.println("Record split points: "
- + (System.currentTimeMillis() - start) + " ms");
-
- toReturn.getArtifacts().add(
- new StandardCompilationAnalysis(dependencies, stories, splitPoints));
-
- System.out.println("Completed SOYC phase in "
- + (System.currentTimeMillis() - soycStart) + " ms");
- }
+ toReturn.getArtifacts().add(
+ makeSoycArtifact(logger, permutationId, jprogram, js, sourceInfoMaps));
System.out.println("Permutation took "
+ (System.currentTimeMillis() - permStart) + " ms");
@@ -508,6 +464,8 @@
// Fix up GWT.runAsync()
if (options.isAggressivelyOptimize() && options.isRunAsyncEnabled()) {
ReplaceRunAsyncs.exec(logger, jprogram);
+ CodeSplitter.pickInitialLoadSequence(logger, jprogram,
+ module.getProperties());
}
// Resolve entry points, rebinding non-static entry points.
@@ -908,6 +866,43 @@
}
}
+ private static StandardCompilationAnalysis makeSoycArtifact(
+ TreeLogger logger, int permutationId, JProgram jprogram, String[] js,
+ List<Map<Range, SourceInfo>> sourceInfoMaps) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ PerfLogger.start("Computing SOYC output");
+
+ PerfLogger.start("Record split points");
+ SplitPointRecorder.recordSplitPoints(jprogram, baos, logger);
+ SoycArtifact splitPoints = new SoycArtifact("splitPoints" + permutationId
+ + ".xml.gz", baos.toByteArray());
+ PerfLogger.end();
+
+ SoycArtifact stories = null;
+ SoycArtifact dependencies = null;
+
+ if (sourceInfoMaps != null) {
+ PerfLogger.start("Record stories");
+ baos.reset();
+ StoryRecorder.recordStories(logger, baos, sourceInfoMaps, js);
+ stories = new SoycArtifact("stories" + permutationId + ".xml.gz",
+ baos.toByteArray());
+ PerfLogger.end();
+
+ PerfLogger.start("Record dependencies");
+ baos.reset();
+ DependencyRecorder.recordDependencies(logger, baos, jprogram);
+ dependencies = new SoycArtifact("dependencies" + permutationId
+ + ".xml.gz", baos.toByteArray());
+ PerfLogger.end();
+ }
+
+ PerfLogger.end();
+
+ return new StandardCompilationAnalysis(dependencies, stories, splitPoints);
+ }
+
/**
* Create a variable assignment to invoke a call to the statistics collector.
*
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 49ac032..99c38f0 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
@@ -24,6 +24,10 @@
import com.google.gwt.dev.jjs.ast.js.JClassSeed;
import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.jjs.ast.js.JsonObject;
+import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs;
+import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement;
+import com.google.gwt.dev.util.collect.Lists;
+import com.google.gwt.dev.util.collect.Maps;
import java.io.IOException;
import java.io.ObjectInputStream;
@@ -223,7 +227,12 @@
private Map<JReferenceType, Integer> queryIds;
- private Map<Integer, String> splitPointMap = new TreeMap<Integer, String>();
+ /**
+ * Filled in by ReplaceRunAsync, once the numbers are known.
+ */
+ private Map<Integer, RunAsyncReplacement> runAsyncReplacements = Maps.create();
+
+ private List<Integer> splitPointInitialSequence = Lists.create();
private final Map<JMethod, JMethod> staticToInstanceMap = new IdentityHashMap<JMethod, JMethod>();
@@ -838,8 +847,12 @@
return integer.intValue();
}
- public Map<Integer, String> getSplitPointMap() {
- return splitPointMap;
+ public Map<Integer, RunAsyncReplacement> getRunAsyncReplacements() {
+ return runAsyncReplacements;
+ }
+
+ public List<Integer> getSplitPointInitialSequence() {
+ return splitPointInitialSequence;
}
public JMethod getStaticImpl(JMethod method) {
@@ -1032,8 +1045,14 @@
this.queryIds = queryIds;
}
- public void setSplitPointMap(Map<Integer, String> splitPointMap) {
- this.splitPointMap = splitPointMap;
+ public void setRunAsyncReplacements(Map<Integer, RunAsyncReplacement> map) {
+ assert runAsyncReplacements.isEmpty();
+ runAsyncReplacements = map;
+ }
+
+ public void setSplitPointInitialSequence(List<Integer> list) {
+ assert splitPointInitialSequence.isEmpty();
+ splitPointInitialSequence.addAll(list);
}
/**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
index df97776..079843e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
@@ -16,8 +16,14 @@
package com.google.gwt.dev.jjs.impl;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
+import com.google.gwt.dev.cfg.Properties;
+import com.google.gwt.dev.cfg.Property;
+import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.HasEnclosingType;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JExpression;
@@ -34,6 +40,7 @@
import com.google.gwt.dev.jjs.impl.FragmentExtractor.LivenessPredicate;
import com.google.gwt.dev.jjs.impl.FragmentExtractor.NothingAlivePredicate;
import com.google.gwt.dev.jjs.impl.FragmentExtractor.StatementLogger;
+import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
@@ -42,6 +49,7 @@
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsVars.JsVar;
+import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.dev.util.PerfLogger;
import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.dev.util.collect.HashSet;
@@ -53,8 +61,6 @@
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* <p>
@@ -185,8 +191,7 @@
}
}
- private static final Pattern LOADER_CLASS_PATTERN = Pattern.compile(FragmentLoaderCreator.ASYNC_LOADER_PACKAGE
- + "." + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX + "([0-9]+)");
+ private static final String PROP_INITIAL_SEQUENCE = "compiler.splitpoint.initial.sequence";
/**
* A Java property that causes the fragment map to be logged.
@@ -204,14 +209,13 @@
}
public static void exec(TreeLogger logger, JProgram jprogram,
- JsProgram jsprogram, JavaToJavaScriptMap map,
- LinkedHashSet<Integer> initialLoadSequence) {
+ JsProgram jsprogram, JavaToJavaScriptMap map) {
if (jprogram.entryMethods.size() == 1) {
// Don't do anything if there is no call to runAsync
return;
}
- new CodeSplitter(logger, jprogram, jsprogram, map, initialLoadSequence).execImpl();
+ new CodeSplitter(logger, jprogram, jsprogram, map).execImpl();
}
public static int getExclusiveFragmentNumber(int splitPoint,
@@ -242,37 +246,47 @@
* other split points. As a side effect, modifies
* {@link com.google.gwt.core.client.impl.AsyncFragmentLoader#initialLoadSequence}
* in the program being compiled.
+ *
+ * @throws UnableToCompleteException If the module specifies a bad load order
*/
- public static LinkedHashSet<Integer> pickInitialLoadSequence(
- TreeLogger logger, JProgram program) {
+ public static void pickInitialLoadSequence(TreeLogger logger,
+ JProgram program, Properties properties) throws UnableToCompleteException {
+ TreeLogger branch = logger.branch(TreeLogger.TRACE,
+ "Looking up initial load sequence for split points");
+ Map<JMethod, List<Integer>> reversedRunAsyncMap = reverse(program.getRunAsyncReplacements());
+
LinkedHashSet<Integer> initialLoadSequence = new LinkedHashSet<Integer>();
- int numSplitPoints = program.entryMethods.size() - 1;
- if (numSplitPoints != 0) {
- Map<Integer, JMethod> splitPointToMethod = findRunAsyncMethods(program);
- assert (splitPointToMethod.size() == numSplitPoints);
-
- ControlFlowAnalyzer cfa = computeInitiallyLive(program);
-
- while (true) {
- Set<Integer> nextSplitPoints = splitPointsReachable(cfa,
- splitPointToMethod, numSplitPoints);
- nextSplitPoints.removeAll(initialLoadSequence);
-
- if (nextSplitPoints.size() != 1) {
- break;
- }
-
- int nextSplitPoint = nextSplitPoints.iterator().next();
- initialLoadSequence.add(nextSplitPoint);
- CodeSplitter.traverseEntry(program, cfa, nextSplitPoint);
+ ConfigurationProperty prop;
+ {
+ Property p = properties.find(PROP_INITIAL_SEQUENCE);
+ if (p == null) {
+ throw new InternalCompilerException(
+ "Could not find configuration property " + PROP_INITIAL_SEQUENCE);
}
-
- logInitialLoadSequence(logger, initialLoadSequence);
- installInitialLoadSequenceField(program, initialLoadSequence);
+ if (!(p instanceof ConfigurationProperty)) {
+ throw new InternalCompilerException(PROP_INITIAL_SEQUENCE
+ + " is not a configuration property");
+ }
+ prop = (ConfigurationProperty) p;
}
- return initialLoadSequence;
+ for (String refString : prop.getValues()) {
+ int splitPoint = findSplitPoint(refString, program, branch,
+ reversedRunAsyncMap);
+ if (initialLoadSequence.contains(splitPoint)) {
+ branch.log(TreeLogger.ERROR, "Split point specified more than once: "
+ + refString);
+ }
+ initialLoadSequence.add(splitPoint);
+ }
+
+ // TODO(spoon) create an artifact in the aux dir describing the choice, so
+ // that SOYC can use it
+ logInitialLoadSequence(logger, initialLoadSequence);
+ installInitialLoadSequenceField(program, initialLoadSequence);
+ program.setSplitPointInitialSequence(new ArrayList<Integer>(
+ initialLoadSequence));
}
/**
@@ -324,30 +338,60 @@
}
/**
- * Maps each split point number to its corresponding generated
- * <code>runAsync</code> method. If that method has been discarded, then map
- * the split point number to <code>null</code>.
+ * Find a split point as designated in the {@link #PROP_INITIAL_SEQUENCE}
+ * configuration property.
+ *
+ * TODO(spoon) accept a labeled runAsync call, once runAsyncs can be labeled
*/
- private static Map<Integer, JMethod> findRunAsyncMethods(JProgram program)
- throws NumberFormatException {
- Map<Integer, JMethod> splitPointToLoadMethod = new HashMap<Integer, JMethod>();
- // These methods aren't indexed, so scan the whole program
-
- for (JDeclaredType type : program.getDeclaredTypes()) {
- Matcher matcher = LOADER_CLASS_PATTERN.matcher(type.getName());
- if (matcher.matches()) {
- int sp = Integer.parseInt(matcher.group(1));
- JMethod loadMethod = null;
- for (JMethod meth : type.getMethods()) {
- if (meth.getName().equals(
- FragmentLoaderCreator.LOADER_METHOD_RUN_ASYNC)) {
- loadMethod = meth;
- }
- }
- splitPointToLoadMethod.put(sp, loadMethod);
+ private static int findSplitPoint(String refString, JProgram program,
+ TreeLogger branch, Map<JMethod, List<Integer>> reversedRunAsyncMap)
+ throws UnableToCompleteException {
+ if (refString.startsWith("@")) {
+ JsniRef jsniRef = JsniRef.parse(refString);
+ if (jsniRef == null) {
+ branch.log(TreeLogger.ERROR, "Badly formatted JSNI reference in "
+ + PROP_INITIAL_SEQUENCE + ": " + refString);
+ throw new UnableToCompleteException();
}
+ final String lookupErrorHolder[] = new String[1];
+ HasEnclosingType referent = JsniRefLookup.findJsniRefTarget(jsniRef,
+ program, new JsniRefLookup.ErrorReporter() {
+ public void reportError(String error) {
+ lookupErrorHolder[0] = error;
+ }
+ });
+ if (referent == null) {
+ TreeLogger resolveLogger = branch.branch(TreeLogger.ERROR,
+ "Could not resolve JSNI reference: " + jsniRef);
+ resolveLogger.log(TreeLogger.ERROR, lookupErrorHolder[0]);
+ throw new UnableToCompleteException();
+ }
+
+ if (!(referent instanceof JMethod)) {
+ branch.log(TreeLogger.ERROR, "Not a method: " + referent);
+ throw new UnableToCompleteException();
+ }
+
+ JMethod method = (JMethod) referent;
+ List<Integer> splitPoints = reversedRunAsyncMap.get(method);
+ if (splitPoints == null) {
+ branch.log(TreeLogger.ERROR,
+ "Method does not enclose a runAsync call: " + jsniRef);
+ throw new UnableToCompleteException();
+ }
+ if (splitPoints.size() != 1) {
+ branch.log(TreeLogger.ERROR,
+ "Method includes multiple runAsync calls, "
+ + "so it's ambiguous which one is meant: " + jsniRef);
+ throw new UnableToCompleteException();
+ }
+
+ return splitPoints.get(0);
}
- return splitPointToLoadMethod;
+
+ branch.log(TreeLogger.ERROR, "Unrecognized designation of a split point: "
+ + refString);
+ throw new UnableToCompleteException();
}
private static String fullNameString(JField field) {
@@ -403,24 +447,22 @@
}
/**
- * Find all split points reachable in the specified ControlFlowAnalyzer.
- *
- * @param cfa the control-flow analyzer to search
- * @param splitPointToMethod a map from split points to methods, computed with
- * {@link #findRunAsyncMethods(JProgram)}.
- * @param numSplitPoints the number of split points in the program
+ * Reverses a runAsync map, returning a map from methods to the split point
+ * numbers invoked from within that method.
*/
- private static Set<Integer> splitPointsReachable(ControlFlowAnalyzer cfa,
- Map<Integer, JMethod> splitPointToMethod, int numSplitPoints) {
- Set<Integer> nextSplitPoints = new HashSet<Integer>();
-
- for (int sp = 1; sp <= numSplitPoints; sp++) {
- if (cfa.getLiveFieldsAndMethods().contains(splitPointToMethod.get(sp))) {
- nextSplitPoints.add(sp);
+ private static Map<JMethod, List<Integer>> reverse(
+ Map<Integer, RunAsyncReplacement> runAsyncMap) {
+ Map<JMethod, List<Integer>> revmap = new HashMap<JMethod, List<Integer>>();
+ for (RunAsyncReplacement replacement : runAsyncMap.values()) {
+ JMethod method = replacement.getEnclosingMethod();
+ List<Integer> list = revmap.get(method);
+ if (list == null) {
+ list = new ArrayList<Integer>();
+ revmap.put(method, list);
}
+ list.add(replacement.getNumber());
}
-
- return nextSplitPoints;
+ return revmap;
}
/**
@@ -488,14 +530,14 @@
private final int numEntries;
private CodeSplitter(TreeLogger logger, JProgram jprogram,
- JsProgram jsprogram, JavaToJavaScriptMap map,
- LinkedHashSet<Integer> initialLoadSequence) {
+ JsProgram jsprogram, JavaToJavaScriptMap map) {
this.logger = logger.branch(TreeLogger.TRACE,
"Splitting JavaScript for incremental download");
this.jprogram = jprogram;
this.jsprogram = jsprogram;
this.map = map;
- this.initialLoadSequence = initialLoadSequence;
+ this.initialLoadSequence = new LinkedHashSet<Integer>(
+ jprogram.getSplitPointInitialSequence());
numEntries = jprogram.entryMethods.size();
logging = Boolean.getBoolean(PROP_LOG_FRAGMENT_MAP);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index 21dd71f..fec6401 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -32,7 +32,6 @@
import com.google.gwt.dev.jjs.ast.JCaseStatement;
import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JCharLiteral;
-import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConditional;
import com.google.gwt.dev.jjs.ast.JContinueStatement;
@@ -193,8 +192,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Queue;
-import java.util.TreeSet;
/**
* This is the big kahuna where most of the nitty gritty of creating our AST
@@ -2680,7 +2677,7 @@
}
}
- private HasEnclosingType parseJsniRef(SourceInfo info, String ident) {
+ private HasEnclosingType findJsniRefTarget(final SourceInfo info, String ident) {
JsniRef parsed = JsniRef.parse(ident);
if (parsed == null) {
reportJsniError(info, methodDecl,
@@ -2688,109 +2685,14 @@
return null;
}
- String className = parsed.className();
- JType type = null;
- if (!className.equals("null")) {
- type = program.getTypeFromJsniRef(className);
- if (type == null) {
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to type '" + className + "'");
- return null;
- }
- }
+ JProgram prog = program;
- if (!parsed.isMethod()) {
- // look for a field
- String fieldName = parsed.memberName();
- if (type == null) {
- if (fieldName.equals("nullField")) {
- return program.getNullField();
- }
-
- } else if (fieldName.equals("class")) {
- JClassLiteral lit = program.getLiteralClass(type);
- return lit.getField();
-
- } else if (type instanceof JPrimitiveType) {
- reportJsniError(info, methodDecl,
- "May not refer to fields on primitive types");
- return null;
-
- } else if (type instanceof JArrayType) {
- reportJsniError(info, methodDecl,
- "May not refer to fields on array types");
- return null;
-
- } else {
- for (JField field : ((JDeclaredType) type).getFields()) {
- if (field.getName().equals(fieldName)) {
- return field;
+ return JsniRefLookup.findJsniRefTarget(parsed, prog,
+ new JsniRefLookup.ErrorReporter() {
+ public void reportError(String error) {
+ reportJsniError(info, methodDecl, error);
}
- }
- }
-
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to field '" + fieldName
- + "' in type '" + className + "'");
- return null;
-
- } else if (type instanceof JPrimitiveType) {
- reportJsniError(info, methodDecl,
- "May not refer to methods on primitive types");
- return null;
-
- } else {
- // look for a method
- TreeSet<String> almostMatches = new TreeSet<String>();
- String methodName = parsed.memberName();
- String jsniSig = parsed.memberSignature();
- if (type == null) {
- if (jsniSig.equals("nullMethod()")) {
- return program.getNullMethod();
- }
- } else {
- Queue<JDeclaredType> workList = new LinkedList<JDeclaredType>();
- workList.add((JDeclaredType) type);
- while (!workList.isEmpty()) {
- JDeclaredType cur = workList.poll();
- for (JMethod method : cur.getMethods()) {
- if (method.getName().equals(methodName)) {
- String sig = JProgram.getJsniSig(method);
- if (sig.equals(jsniSig)) {
- return method;
- } else if (sig.startsWith(jsniSig) && jsniSig.endsWith(")")) {
- return method;
- } else {
- almostMatches.add(sig);
- }
- }
- }
- if (cur.getSuperClass() != null) {
- workList.add(cur.getSuperClass());
- }
- workList.addAll(cur.getImplements());
- }
- }
-
- if (almostMatches.isEmpty()) {
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to method '" + methodName
- + "' in type '" + className + "'");
- return null;
- } else {
- StringBuilder suggestList = new StringBuilder();
- String comma = "";
- for (String almost : almostMatches) {
- suggestList.append(comma + "'" + almost + "'");
- comma = ", ";
- }
- reportJsniError(info, methodDecl,
- "Unresolvable native reference to method '" + methodName
- + "' in type '" + className + "' (did you mean "
- + suggestList.toString() + "?)");
- return null;
- }
- }
+ });
}
private void processField(JsNameRef nameRef, SourceInfo info,
@@ -2887,7 +2789,7 @@
String ident = nameRef.getIdent();
HasEnclosingType node = program.jsniMap.get(ident);
if (node == null) {
- node = parseJsniRef(info, ident);
+ node = findJsniRefTarget(info, ident);
if (node == null) {
return; // already reported error
}
@@ -3004,5 +2906,4 @@
}
return false;
}
-
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java
new file mode 100644
index 0000000..7a540fc
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.HasEnclosingType;
+import com.google.gwt.dev.jjs.ast.JArrayType;
+import com.google.gwt.dev.jjs.ast.JClassLiteral;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.util.JsniRef;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.TreeSet;
+
+/**
+ * A utility class that can look up a {@link JsniRef} in a {@link JProgram}.
+ */
+public class JsniRefLookup {
+ public interface ErrorReporter {
+ void reportError(String error);
+ }
+
+ /**
+ * Look up a JSNI reference.
+ *
+ * @param ref The reference to look up
+ * @param program The program to look up the reference in
+ * @param errorReporter A callback used to indicate the reason for a failed
+ * JSNI lookup
+ * @return The item referred to, or <code>null</code> if it could not be
+ * found. If the return value is <code>null</code>,
+ * <code>errorReporter</code> will have been invoked.
+ */
+ public static HasEnclosingType findJsniRefTarget(JsniRef ref,
+ JProgram program, JsniRefLookup.ErrorReporter errorReporter) {
+ String className = ref.className();
+ JType type = null;
+ if (!className.equals("null")) {
+ type = program.getTypeFromJsniRef(className);
+ if (type == null) {
+ errorReporter.reportError("Unresolvable native reference to type '"
+ + className + "'");
+ return null;
+ }
+ }
+
+ if (!ref.isMethod()) {
+ // look for a field
+ String fieldName = ref.memberName();
+ if (type == null) {
+ if (fieldName.equals("nullField")) {
+ return program.getNullField();
+ }
+
+ } else if (fieldName.equals("class")) {
+ JClassLiteral lit = program.getLiteralClass(type);
+ return lit.getField();
+
+ } else if (type instanceof JPrimitiveType) {
+ errorReporter.reportError("May not refer to fields on primitive types");
+ return null;
+
+ } else if (type instanceof JArrayType) {
+ errorReporter.reportError("May not refer to fields on array types");
+ return null;
+
+ } else {
+ for (JField field : ((JDeclaredType) type).getFields()) {
+ if (field.getName().equals(fieldName)) {
+ return field;
+ }
+ }
+ }
+
+ errorReporter.reportError("Unresolvable native reference to field '"
+ + fieldName + "' in type '" + className + "'");
+ return null;
+
+ } else if (type instanceof JPrimitiveType) {
+ errorReporter.reportError("May not refer to methods on primitive types");
+ return null;
+
+ } else {
+ // look for a method
+ TreeSet<String> almostMatches = new TreeSet<String>();
+ String methodName = ref.memberName();
+ String jsniSig = ref.memberSignature();
+ if (type == null) {
+ if (jsniSig.equals("nullMethod()")) {
+ return program.getNullMethod();
+ }
+ } else {
+ Queue<JDeclaredType> workList = new LinkedList<JDeclaredType>();
+ workList.add((JDeclaredType) type);
+ while (!workList.isEmpty()) {
+ JDeclaredType cur = workList.poll();
+ for (JMethod method : cur.getMethods()) {
+ if (method.getName().equals(methodName)) {
+ String sig = JProgram.getJsniSig(method);
+ if (sig.equals(jsniSig)) {
+ return method;
+ } else if (sig.startsWith(jsniSig) && jsniSig.endsWith(")")) {
+ return method;
+ } else {
+ almostMatches.add(sig);
+ }
+ }
+ }
+ if (cur.getSuperClass() != null) {
+ workList.add(cur.getSuperClass());
+ }
+ workList.addAll(cur.getImplements());
+ }
+ }
+
+ if (almostMatches.isEmpty()) {
+ errorReporter.reportError("Unresolvable native reference to method '"
+ + methodName + "' in type '" + className + "'");
+ return null;
+ } else {
+ StringBuilder suggestList = new StringBuilder();
+ String comma = "";
+ for (String almost : almostMatches) {
+ suggestList.append(comma + "'" + almost + "'");
+ comma = ", ";
+ }
+ errorReporter.reportError("Unresolvable native reference to method '"
+ + methodName + "' in type '" + className + "' (did you mean "
+ + suggestList.toString() + "?)");
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
index b6f6c65..2894980 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
@@ -26,20 +26,61 @@
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JType;
+import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
-import java.util.TreeMap;
/**
* Replaces calls to
- * {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback)}"
+ * {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback)}
* by calls to a fragment loader.
*/
public class ReplaceRunAsyncs {
+ /**
+ * Information about the replacement of one runAsync call by a call to a
+ * generated code-loading method.
+ */
+ public static class RunAsyncReplacement implements Serializable {
+ private final int number;
+ private final JMethod enclosingMethod;
+ private final JMethod loadMethod;
+
+ RunAsyncReplacement(int number, JMethod enclosingMethod, JMethod loadMethod) {
+ this.number = number;
+ this.enclosingMethod = enclosingMethod;
+ this.loadMethod = loadMethod;
+ }
+
+ @Override
+ public String toString() {
+ return "#" + number + ": " + enclosingMethod.toString();
+ }
+
+ /**
+ * The index of this runAsync, numbered from 1 to n.
+ */
+ public int getNumber() {
+ return number;
+ }
+
+ /**
+ * Can be null if the enclosing method cannot be designated with a JSNI
+ * reference.
+ */
+ public JMethod getEnclosingMethod() {
+ return enclosingMethod;
+ }
+
+ /**
+ * The load method to request loading the code for this method.
+ */
+ public JMethod getLoadMethod() {
+ return loadMethod;
+ }
+ }
+
private class AsyncCreateVisitor extends JModVisitor {
private JMethod currentMethod;
- private Map<Integer, String> splitPointMap = new TreeMap<Integer, String>();
- private Map<String, Integer> methodCount = new HashMap<String, Integer>();
private int entryCount = 1;
@Override
@@ -50,23 +91,11 @@
JExpression asyncCallback = x.getArgs().get(0);
int entryNumber = entryCount++;
- logger.log(TreeLogger.DEBUG, "Assigning split point #" + entryNumber
- + " in method " + fullMethodDescription(currentMethod));
-
- String methodDescription = fullMethodDescription(currentMethod);
- if (methodCount.containsKey(methodDescription)) {
- methodCount.put(methodDescription,
- methodCount.get(methodDescription) + 1);
- methodDescription += "#"
- + Integer.toString(methodCount.get(methodDescription));
- } else {
- methodCount.put(methodDescription, 1);
- }
- splitPointMap.put(entryNumber, methodDescription);
-
JClassType loader = getFragmentLoader(entryNumber);
JMethod loadMethod = getRunAsyncMethod(loader);
assert loadMethod != null;
+ runAsyncReplacements.put(entryNumber, new RunAsyncReplacement(
+ entryNumber, currentMethod, loadMethod));
JMethodCall methodCall = new JMethodCall(x.getSourceInfo(), null,
loadMethod);
@@ -85,30 +114,24 @@
}
}
- public static int exec(TreeLogger logger, JProgram program) {
- return new ReplaceRunAsyncs(logger, program).execImpl();
- }
-
- private static String fullMethodDescription(JMethod method) {
- return (method.getEnclosingType().getName() + "." + JProgram.getJsniSig(method));
- }
-
- private final TreeLogger logger;
- private JProgram program;
-
- private ReplaceRunAsyncs(TreeLogger logger, JProgram program) {
- this.logger = logger.branch(TreeLogger.TRACE,
+ public static void exec(TreeLogger logger, JProgram program) {
+ logger.log(TreeLogger.TRACE,
"Replacing GWT.runAsync with island loader calls");
+ new ReplaceRunAsyncs(program).execImpl();
+ }
+
+ private JProgram program;
+ private Map<Integer, RunAsyncReplacement> runAsyncReplacements = new HashMap<Integer, RunAsyncReplacement>();
+
+ private ReplaceRunAsyncs(JProgram program) {
this.program = program;
}
- private int execImpl() {
+ private void execImpl() {
AsyncCreateVisitor visitor = new AsyncCreateVisitor();
visitor.accept(program);
setNumEntriesInAsyncFragmentLoader(visitor.entryCount);
- program.setSplitPointMap(visitor.splitPointMap);
-
- return visitor.entryCount;
+ program.setRunAsyncReplacements(runAsyncReplacements);
}
private JClassType getFragmentLoader(int fragmentNumber) {
diff --git a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
index a1a1e94..f56fa77 100644
--- a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
+++ b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
@@ -12,13 +12,23 @@
<!-- implied. License for the specific language governing permissions and -->
<!-- limitations under the License. -->
-<!-- Compiler parameters that can be overridden for test cases . -->
+<!-- Compiler parameters that can be overridden . -->
<!-- -->
<module>
<!--
- This is the maximum number of variables in any var statement GWT will emit. This avoids a bug in
- some browsers including the initial beta of Safari 4. See Issue 3455. If it is set to -1, then
- there is no limit.
+ A user-specified initial load sequence for the runAsync calls. Each entry should specify the
+ surrounding method immediately enclosing the call, using a full JSNI reference.
+ -->
+ <define-configuration-property name='compiler.splitpoint.initial.sequence'
+ is-multi-valued='true' />
+
+ <!-- From here down, the properties are unsupported and are only available for test cases -->
+
+ <!--
+ This is the maximum number of variables in any var statement GWT
+ will emit. This avoids a bug in some browsers including
+ the initial beta of Safari 4. See Issue 3455. If it is set to -1,
+ then there is no limit.
-->
<define-configuration-property name='compiler.max.vars.per.var'
is-multi-valued='false' />
diff --git a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
index b9089e0..490a502 100644
--- a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
@@ -414,6 +414,17 @@
}
remainingInitialFragments.push(leftoversFragment());
}
+
+ if (initialFragmentErrorHandlers.isEmpty()
+ && waitingForInitialFragmentsErrorHandlers.isEmpty()
+ && remainingInitialFragments.length() > 1) {
+ /*
+ * No further requests are pending, and more than the leftovers fragment
+ * is left outstanding. Stop loading stuff for now.
+ */
+ initialFragmentsLoading = false;
+ return;
+ }
if (remainingInitialFragments.length() > 0) {
// start loading the next initial fragment