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
-~----------~----~----~----~------~----~------~--~---

Reply via email to