Author: sp...@google.com Date: Mon Jun 15 17:20:03 2009 New Revision: 5560
Added: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java (contents, props changed) Modified: trunk/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java trunk/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java trunk/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java Log: 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 Modified: trunk/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java (original) +++ trunk/dev/core/src/com/google/gwt/core/ext/soyc/impl/SplitPointRecorder.java Mon Jun 15 17:20:03 2009 @@ -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(); @@ -94,6 +95,33 @@ } catch (Throwable e) { logger.log(TreeLogger.ERROR, "Could not open dependency file.", e); } + } + + 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() { Modified: trunk/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java (original) +++ trunk/dev/core/src/com/google/gwt/core/linker/SoycReportLinker.java Mon Jun 15 17:20:03 2009 @@ -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; Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java (original) +++ trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Mon Jun 15 17:20:03 2009 @@ -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. @@ -906,6 +864,43 @@ logger.log(TreeLogger.ERROR, "Unexpected internal compiler error", e); return new UnableToCompleteException(); } + } + + 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); } /** Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java (original) +++ trunk/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java Mon Jun 15 17:20:03 2009 @@ -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); } /** Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java (original) +++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java Mon Jun 15 17:20:03 2009 @@ -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) { - 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); + 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()); - ControlFlowAnalyzer cfa = computeInitiallyLive(program); - - while (true) { - Set<Integer> nextSplitPoints = splitPointsReachable(cfa, - splitPointToMethod, numSplitPoints); - nextSplitPoints.removeAll(initialLoadSequence); - - if (nextSplitPoints.size() != 1) { - break; - } + LinkedHashSet<Integer> initialLoadSequence = new LinkedHashSet<Integer>(); - 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); + } + if (!(p instanceof ConfigurationProperty)) { + throw new InternalCompilerException(PROP_INITIAL_SEQUENCE + + " is not a configuration property"); } + prop = (ConfigurationProperty) p; + } - logInitialLoadSequence(logger, initialLoadSequence); - installInitialLoadSequenceField(program, 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); } - return initialLoadSequence; + // 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>. - */ - 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); + * 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 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); Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java (original) +++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java Mon Jun 15 17:20:03 2009 @@ -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; - } - } - - if (!parsed.isMethod()) { - // look for a field - String fieldName = parsed.memberName(); - if (type == null) { - if (fieldName.equals("nullField")) { - return program.getNullField(); - } + JProgram prog = program; - } 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; } - } Added: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java ============================================================================== --- (empty file) +++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/JsniRefLookup.java Mon Jun 15 17:20:03 2009 @@ -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; + } + } + } + +} Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java (original) +++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java Mon Jun 15 17:20:03 2009 @@ -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)); + public static void exec(TreeLogger logger, JProgram program) { + logger.log(TreeLogger.TRACE, + "Replacing GWT.runAsync with island loader calls"); + new ReplaceRunAsyncs(program).execImpl(); } - private final TreeLogger logger; private JProgram program; + private Map<Integer, RunAsyncReplacement> runAsyncReplacements = new HashMap<Integer, RunAsyncReplacement>(); - private ReplaceRunAsyncs(TreeLogger logger, JProgram program) { - this.logger = logger.branch(TreeLogger.TRACE, - "Replacing GWT.runAsync with island loader calls"); + 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) { Modified: trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml ============================================================================== --- trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml (original) +++ trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml Mon Jun 15 17:20:03 2009 @@ -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' /> Modified: trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java ============================================================================== --- trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java (original) +++ trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java Mon Jun 15 17:20:03 2009 @@ -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 --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---