Have the compiler emit a hopefully reasonable measure of script
size for applications that use GWT.runAsync and thus include a large
number of code fragments in the output.

Review by: jat



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5076 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index bd03f86..e4284b4 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -27,6 +27,7 @@
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
 import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.jjs.impl.CodeSplitter;
 import com.google.gwt.dev.util.FileBackedObject;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
@@ -240,6 +241,66 @@
       }
       compilation.addSelectionPermutation(unboundProperties);
     }
+    logScriptSize(logger, perm.getId(), compilation);
+  }
+
+  /**
+   * <p>
+   * Computes and logs the "maximum total script size" for this permutation. The
+   * total script size for one sequence of split points reached is the sum of
+   * the scripts that are downloaded for that sequence. The maximum total script
+   * size is the maximum such size for all possible sequences of split points.
+   * </p>
+   */
+  private static void logScriptSize(TreeLogger logger, int permId,
+      StandardCompilationResult compilation) {
+    /*
+     * The total script size is fully determined by the first split point that
+     * is reached; the order that the remaining are reached doesn't matter. To
+     * find the maximum, divide the sum into two parts: first add the initial
+     * and exclusive fragments, and then calculate the adjustment that should be
+     * applied depending on which split point comes first. Choose among these
+     * adjustments the one that is largest.
+     */
+
+    String[] javaScript = compilation.getJavaScript();
+    int numSplitPoints = CodeSplitter.numSplitPointsForFragments(javaScript.length);
+    int maxTotalSize;
+
+    if (numSplitPoints == 0) {
+      maxTotalSize = javaScript[0].length();
+    } else {
+      // Add up the initial and exclusive fragments
+      maxTotalSize = javaScript[0].length();
+      for (int sp = 1; sp <= numSplitPoints; sp++) {
+        int excl = CodeSplitter.getExclusiveFragmentNumber(sp, numSplitPoints);
+        maxTotalSize += javaScript[excl].length();
+      }
+
+      // Find the largest adjustment for any split point
+      boolean first = true;
+      int adjustment = 0;
+
+      for (int sp = 1; sp <= numSplitPoints; sp++) {
+        int excl = CodeSplitter.getExclusiveFragmentNumber(sp, numSplitPoints);
+        int base = CodeSplitter.getBaseFragmentNumber(sp, numSplitPoints);
+        int leftovers = CodeSplitter.getLeftoversFragmentNumber(sp,
+            numSplitPoints);
+        int thisAdjustment = javaScript[base].length()
+            + javaScript[leftovers].length() - javaScript[excl].length();
+        if (first || (thisAdjustment > adjustment)) {
+          adjustment = thisAdjustment;
+        }
+        first = false;
+      }
+
+      maxTotalSize += adjustment;
+    }
+
+    logger.log(TreeLogger.DEBUG, "Permutation " + permId + " (strong name "
+        + compilation.getStrongName() + ") has an initial download size of "
+        + javaScript[0].length() + " and max total script size of "
+        + maxTotalSize);
   }
 
   private final LinkOptionsImpl options;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
index a44816d..2304cad 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
@@ -227,6 +227,28 @@
     new CodeSplitter(logger, jprogram, jsprogram, map).execImpl();
   }
 
+  public static int getBaseFragmentNumber(int sp, int numSplitPoints) {
+    return numSplitPoints + 2 * sp - 1;
+  }
+
+  public static int getExclusiveFragmentNumber(int splitPoint,
+      int numSplitPoints) {
+    return splitPoint;
+  }
+
+  public static int getLeftoversFragmentNumber(int splitPoint,
+      int numSplitPoints) {
+    return numSplitPoints + 2 * splitPoint;
+  }
+
+  /**
+   * Infer the number of split points for a given number of code fragments.
+   */
+  public static int numSplitPointsForFragments(int codeFragments) {
+    assert (((codeFragments - 1) % 3) == 0);
+    return (codeFragments - 1) / 3;
+  }
+
   private static Map<JField, JClassLiteral> buildFieldToClassLiteralMap(
       JProgram jprogram) {
     final Map<JField, JClassLiteral> map = new HashMap<JField, JClassLiteral>();