Ray Cromwell has uploaded a new change for review.

  https://gwt-review.googlesource.com/2581


Change subject: Adds new configuration property to specify minimum fragment size.
......................................................................

Adds new configuration property to specify minimum fragment size.

<set-configuration-property name="compiler.splitpoint.leftovermerge.size" value="size in bytes"/>

Any exclusive fragment smaller than this limit will be merged into the left overs fragment.

Change-Id: Ifcf035d88db70e890c739eab3997b85fa2e5677e
---
M dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
M dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
M dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java
M dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
M dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
M dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java
M user/src/com/google/gwt/core/CompilerParameters.gwt.xml
7 files changed, 267 insertions(+), 27 deletions(-)



diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index c0d6ebb..3f57ee5 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -597,8 +597,8 @@
     options.produceReferenceInfo = true;

     // Turn off all warnings, saves some memory / speed.
- options.reportUnusedDeclaredThrownExceptionIncludeDocCommentReference = false; - options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false; +// options.reportUnusedDeclaredThrownExceptionIncludeDocCommentReference = false; +// options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false;
     options.warningThreshold = 0;
     options.inlineJsrBytecode = true;
     return options;
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 b7ccabb..197e6a3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -400,7 +400,9 @@
         // merging.
         if (fragmentsMerge > 0) {
CodeSplitter2.exec(logger, jprogram, jsProgram, jjsmap, fragmentsMerge,
-              chooseDependencyRecorder(options.isSoycEnabled(), baos));
+              chooseDependencyRecorder(options.isSoycEnabled(), baos),
+              findIntegerConfigurationProperty(propertyOracles, logger,
+                  CodeSplitter2.LEFTOVERMERGE_SIZE, 0));
         } else {
CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, chooseDependencyRecorder(options
               .isSoycEnabled(), baos));
@@ -569,6 +571,25 @@
     return toReturn;
   }

+  /**
+   * Look for a configuration property in all property oracles.
+   */
+  public static int findIntegerConfigurationProperty(
+      PropertyOracle[] propertyOracles, TreeLogger logger,
+      String name, int def) {
+    int toReturn = def;
+    for (PropertyOracle oracle : propertyOracles) {
+      try {
+ com.google.gwt.core.ext.ConfigurationProperty property = oracle.getConfigurationProperty(name);
+        toReturn = Integer.parseInt(property.getValues().get(0));
+      } catch (Exception e) {
+        break;
+      }
+    }
+    return toReturn;
+  }
+
+
   public static UnifiedAst precompile(TreeLogger logger, ModuleDef module,
RebindPermutationOracle rpo, String[] declEntryPts, String[] additionalRootTypes, JJSOptions options, boolean singlePermutation) throws UnableToCompleteException { diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java
index 2c871f4..897464d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter2.java
@@ -43,6 +43,7 @@
 import com.google.gwt.dev.jjs.impl.FragmentExtractor.CfaLivenessPredicate;
 import com.google.gwt.dev.jjs.impl.FragmentExtractor.LivenessPredicate;
 import com.google.gwt.dev.jjs.impl.FragmentExtractor.NothingAlivePredicate;
+import com.google.gwt.dev.js.JsToStringGenerationVisitor;
 import com.google.gwt.dev.js.ast.JsBlock;
 import com.google.gwt.dev.js.ast.JsContext;
 import com.google.gwt.dev.js.ast.JsModVisitor;
@@ -50,6 +51,7 @@
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.util.JsniRef;
+import com.google.gwt.dev.util.TextOutput;
 import com.google.gwt.dev.util.collect.HashMap;
 import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
@@ -277,7 +279,10 @@
    * The property key for a list of initially loaded split points.
    */
private static final String PROP_INITIAL_SEQUENCE = "compiler.splitpoint.initial.sequence";
-
+
+  public static final String LEFTOVERMERGE_SIZE =
+      "compiler.splitpoint.leftovermerge.size";
+
public static ControlFlowAnalyzer computeInitiallyLive(JProgram jprogram) {
     return computeInitiallyLive(jprogram, CodeSplitter.NULL_RECORDER);
   }
@@ -295,16 +300,19 @@
     return cfa;
   }

- public static void exec(TreeLogger logger, JProgram jprogram, JsProgram jsprogram,
+  public static void exec(TreeLogger logger, JProgram jprogram,
+      JsProgram jsprogram,
       JavaToJavaScriptMap map, int fragmentsToMerge,
-      MultipleDependencyGraphRecorder dependencyRecorder) {
+      MultipleDependencyGraphRecorder dependencyRecorder,
+      int leftOverMergeLimit) {
     if (jprogram.getRunAsyncs().size() == 0) {
       // Don't do anything if there is no call to runAsync
       return;
     }
Event codeSplitterEvent = SpeedTracerLogger.start(CompilerEventType.CODE_SPLITTER);
     new CodeSplitter2(
- logger, jprogram, jsprogram, map, fragmentsToMerge, dependencyRecorder).execImpl();
+        logger, jprogram, jsprogram, map, fragmentsToMerge,
+        dependencyRecorder, leftOverMergeLimit).execImpl();
     codeSplitterEvent.end();
   }

@@ -648,7 +656,8 @@
    * Number of split points to merge.
    */
   private final int splitPointsMerge;
-
+  private int leftOverMergeLimit;
+
   /**
    * Maps the split point index X to Y where where that split point X would
    * appear in the Y.cache.js
@@ -665,12 +674,15 @@
    */
   private final int[] splitPointToFragmentMap;

- private CodeSplitter2(TreeLogger logger, JProgram jprogram, JsProgram jsprogram,
+  private CodeSplitter2(TreeLogger logger, JProgram jprogram,
+      JsProgram jsprogram,
       JavaToJavaScriptMap map, int splitPointsMerge,
-      MultipleDependencyGraphRecorder dependencyRecorder) {
+      MultipleDependencyGraphRecorder dependencyRecorder,
+      int leftOverMergeLimit) {
     this.jprogram = jprogram;
     this.jsprogram = jsprogram;
     this.splitPointsMerge = splitPointsMerge;
+    this.leftOverMergeLimit = leftOverMergeLimit;
this.fragmentExtractor = new FragmentExtractor(jprogram, jsprogram, map); this.initialLoadSequence = new LinkedHashSet<Integer>(jprogram.getSplitPointInitialSequence());

@@ -713,7 +725,100 @@
     stats.addAll(stmtsToAppend);
     fragmentStats.put(splitPoint, stats);
   }
-
+
+  private boolean fragmentSizeBelowMergeLimit(List<JsStatement> stats,
+      final int leftOverMergeLimit) {
+    int sizeInBytes = 0;
+    TextOutput out = new TextOutput() {
+      int count = 0;
+
+      @Override
+      public int getColumn() {
+        return 0;
+      }
+
+      @Override
+      public int getLine() {
+        return 0;
+      }
+
+      @Override
+      public int getPosition() {
+        return count;
+      }
+
+      @Override
+      public void indentIn() {
+      }
+
+      @Override
+      public void indentOut() {
+      }
+
+      @Override
+      public void newline() {
+        inc(1);
+      }
+
+      @Override
+      public void newlineOpt() {
+      }
+
+      @Override
+      public void print(char c) {
+        inc(1);
+      }
+
+      @Override
+      public void print(char[] s) {
+        inc(s.length);
+      }
+
+      @Override
+      public void print(String s) {
+        inc(s.length());
+      }
+
+      private void inc(int length) {
+        count += length;
+        if (count >= leftOverMergeLimit) {
+          // yucky, but necessary, early exit
+          throw new MergeLimitExceededException();
+        }
+      }
+
+      @Override
+      public void printOpt(char c) {
+      }
+
+      @Override
+      public void printOpt(char[] s) {
+      }
+
+      @Override
+      public void printOpt(String s) {
+      }
+    };
+
+    try {
+      JsToStringGenerationVisitor v = new JsToStringGenerationVisitor(out);
+      for (JsStatement stat : stats) {
+        v.accept(stat);
+      }
+      sizeInBytes += out.getPosition();
+    } catch (InternalCompilerException me) {
+      if (me.getCause().getClass() == MergeLimitExceededException.class) {
+        return false;
+      } else {
+        throw me;
+      }
+    }
+    return sizeInBytes < leftOverMergeLimit;
+  }
+
+ private static class MergeLimitExceededException extends RuntimeException {
+  }
+
   private ControlFlowAnalyzer computeAllButNCfas(
       ControlFlowAnalyzer liveAfterInitialSequence, List<Integer> sp) {
List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>();
@@ -867,7 +972,8 @@
       }
     }
     allFields.addAll(everything.getFieldsWritten());
-
+ ArrayList<JsStatement> leftOverMergeStats = new ArrayList<JsStatement>();
+
// Search for all the atoms that are exclusively needed in each split point.
     for (int i = 1; i < splitPointToFragmentMap.length; i++) {

@@ -925,9 +1031,25 @@

LivenessPredicate alreadyLoaded = new ExclusivityMapLivenessPredicate(fragmentMap, 0); LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, i); - List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex);
-      addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
+ List<JsStatement> exclusiveStats = fragmentExtractor.extractStatements(liveNow, alreadyLoaded); + if (fragmentSizeBelowMergeLimit(exclusiveStats, leftOverMergeLimit)) {
+        leftOverMergeStats.addAll(exclusiveStats);
+        // merged to leftovers
+        splitPointToFragmentMap[i] = -1;
+        continue;
+      } else {
+ List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex); + addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
+      }
       cacheIndex++;
+    }
+
+
+    for (int i = 0; i < splitPointToFragmentMap.length; i++) {
+      if (splitPointToFragmentMap[i] == -1) {
+        // set fragment number -1 to be leftovers fragment number
+        splitPointToFragmentMap[i] = splitPointToFragmentMap.length;
+      }
     }

     /*
@@ -937,7 +1059,8 @@
LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(liveAfterInitialSequence); LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, 0); List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex); - addFragment(splitPointToFragmentMap.length, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
+      leftOverMergeStats.addAll(statsToAppend);
+ addFragment(splitPointToFragmentMap.length, alreadyLoaded, liveNow, leftOverMergeStats, fragmentStats);
     }

     // now install the new statements in the program fragments
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
index e471cc3..d7c6953 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
@@ -28,7 +28,6 @@
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsProgramFragment;
-import com.google.gwt.dev.js.ast.JsSeedIdOf;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsVars.JsVar;
 import com.google.gwt.dev.js.ast.JsVisitable;
@@ -82,12 +81,6 @@
   public boolean visit(JsProgramFragment x, JsContext ctx) {
     // Descend naturally.
     return true;
-  }
-
-  @Override
-  public boolean visit(JsSeedIdOf x, JsContext ctx) {
-    out.print(String.valueOf(x.getSeedId()));
-    return false;
   }

   @Override
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index a10de34..ed1f569 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -59,6 +59,7 @@
 import com.google.gwt.dev.js.ast.JsPropertyInitializer;
 import com.google.gwt.dev.js.ast.JsRegExp;
 import com.google.gwt.dev.js.ast.JsReturn;
+import com.google.gwt.dev.js.ast.JsSeedIdOf;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsStringLiteral;
 import com.google.gwt.dev.js.ast.JsSwitch;
@@ -896,6 +897,12 @@
   }

   @Override
+  public boolean visit(JsSeedIdOf x, JsContext ctx) {
+    p.print(String.valueOf(x.getSeedId()));
+    return false;
+  }
+
+  @Override
   public boolean visit(JsStringLiteral x, JsContext ctx) {
     printStringLiteral(x.getValue());
     return false;
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java b/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java
index 162e3d8..105b169 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitter2Test.java
@@ -15,9 +15,6 @@
  */
 package com.google.gwt.dev.jjs.impl;

-import java.util.Map;
-import java.util.TreeMap;
-
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.SymbolData;
@@ -38,6 +35,9 @@
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsVisitor;
+
+import java.util.Map;
+import java.util.TreeMap;

 /**
  * Unit test for {@link CodeSplitter2}.
@@ -104,7 +104,63 @@
     // functionC must be in the initial fragment.
     assertInFragment("functionC", 0);
   }
-
+
+  public void testMergeLeftOvers() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("package test;\n");
+    code.append("import com.google.gwt.core.client.GWT;\n");
+    code.append("import com.google.gwt.core.client.RunAsyncCallback;\n");
+    code.append("public class EntryPoint {\n");
+    code.append(functionA);
+    code.append(functionB);
+    code.append(functionC);
+    code.append("  public static void onModuleLoad() {\n");
+    // Fragment #1
+    code.append(createRunAsync("functionA();"));
+    // Fragment #2
+    code.append(createRunAsync("functionB();"));
+    // Fragment #3
+    code.append(createRunAsync("functionC();"));
+    code.append("  }\n");
+    code.append("}\n");
+    compileSnippetWithLeftoverMerge(code.toString(),
+        10 * 1024 /* 10k minumum */);
+
+    // init + leftover.
+    assertFragmentCount(2);
+    assertInFragment("functionA", 1);
+    assertInFragment("functionB", 1);
+    assertInFragment("functionC", 1);
+  }
+
+  public void testDontMergeLeftOvers() throws UnableToCompleteException {
+    StringBuffer code = new StringBuffer();
+    code.append("package test;\n");
+    code.append("import com.google.gwt.core.client.GWT;\n");
+    code.append("import com.google.gwt.core.client.RunAsyncCallback;\n");
+    code.append("public class EntryPoint {\n");
+    code.append(functionA);
+    code.append(functionB);
+    code.append(functionC);
+    code.append("  public static void onModuleLoad() {\n");
+    // Fragment #1
+    code.append(createRunAsync("functionA();"));
+    // Fragment #2
+    code.append(createRunAsync("functionB();"));
+    // Fragment #3
+    code.append(createRunAsync("functionC();"));
+    code.append("  }\n");
+    code.append("}\n");
+    // we want don't want them to be merged
+    compileSnippetWithLeftoverMerge(code.toString(), 10);
+
+    // init + leftover.
+    assertFragmentCount(5);
+    assertNotInFragment("functionA", 4);
+    assertNotInFragment("functionB", 4);
+    assertNotInFragment("functionC", 4);
+  }
+
   public void testNoMergeMoreThanTwo() throws UnableToCompleteException {
     StringBuffer code = new StringBuffer();
     code.append("package test;\n");
@@ -217,8 +273,41 @@
     JavaToJavaScriptMap map = GenerateJavaScriptAST.exec(
jProgram, jsProgram, JsOutputOption.PRETTY, symbolTable, new PropertyOracle[]{ new StaticPropertyOracle(orderedProps, orderedPropValues, configProps)}).getLeft();
-    CodeSplitter2.exec(logger, jProgram, jsProgram, map, 4, null);
+    CodeSplitter2.exec(logger, jProgram, jsProgram, map, 4, null, 0);
   }
+
+  /**
+ * Compiles a Java class <code>test.EntryPoint</code> and use the code splitter on it
+   * with leftover merge enabled.
+   */
+  protected void compileSnippetWithLeftoverMerge(final String code,
+      int mergeLimit) throws UnableToCompleteException {
+    addMockIntrinsic();
+    sourceOracle.addOrReplace(new MockJavaResource("test.EntryPoint") {
+      @Override
+      public CharSequence getContent() {
+        return code;
+      }
+    });
+    addBuiltinClasses(sourceOracle);
+    CompilationState state =
+ CompilationStateBuilder.buildFrom(logger, sourceOracle.getResources(),
+            getAdditionalTypeProviderDelegate());
+    jProgram =
+        JavaAstConstructor.construct(logger, state, "test.EntryPoint",
+            "com.google.gwt.lang.Exceptions");
+    jProgram.addEntryMethod(findMethod(jProgram, "onModuleLoad"));
+    CastNormalizer.exec(jProgram, false);
+    ArrayNormalizer.exec(jProgram);
+    Map<StandardSymbolData, JsName> symbolTable =
+ new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator());
+    JavaToJavaScriptMap map = GenerateJavaScriptAST.exec(
+ jProgram, jsProgram, JsOutputOption.PRETTY, symbolTable, new PropertyOracle[]{ + new StaticPropertyOracle(orderedProps, orderedPropValues, configProps)}).getLeft();
+    CodeSplitter2.exec(logger, jProgram, jsProgram, map, 4, null,
+        mergeLimit);
+  }
+

   private static String createRunAsync(String body) {
     return "GWT.runAsync(new RunAsyncCallback() {" +
diff --git a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
index 81435a2..72acb09 100644
--- a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
+++ b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
@@ -23,6 +23,13 @@
     is-multi-valued='true' />

   <!--
+ A user-specified limit in bytes where fragments smaller than the limit are
+   rolled into the leftovers fragment.
+   -->
+ <define-configuration-property name='compiler.splitpoint.leftovermerge.size'
+                                   is-multi-valued='false' />
+
+  <!--
Whether or not the compiler should predeclare variables that are defined
     outside the initial download and are referenced from a different code
fragment than the one defining them. This is usually determined by which

--
To view, visit https://gwt-review.googlesource.com/2581
To unsubscribe, visit https://gwt-review.googlesource.com/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ifcf035d88db70e890c739eab3997b85fa2e5677e
Gerrit-PatchSet: 1
Gerrit-Project: gwt
Gerrit-Branch: master
Gerrit-Owner: Ray Cromwell <cromwell...@google.com>

--
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors
--- You received this message because you are subscribed to the Google Groups "Google Web Toolkit Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to google-web-toolkit-contributors+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to