Author: [email protected] Date: Wed Jun 3 16:36:00 2009 New Revision: 5504 Added: branches/snapshot-2009.06.02-r5498/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java - copied unchanged from r5488, /branches/snapshot-2009.05.12-r5406/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java Removed: branches/snapshot-2009.06.02-r5498/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java Modified: branches/snapshot-2009.06.02-r5498/branch-info.txt branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/Link.java branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
Log: Merged r5488 from the 5/12 snapshot to revert runAsync performance regression. svn merge -c5488 https://google-web-toolkit.googlecode.com/svn/branches/snapshot-2009.05.12-r5406 . Modified: branches/snapshot-2009.06.02-r5498/branch-info.txt ============================================================================== --- branches/snapshot-2009.06.02-r5498/branch-info.txt (original) +++ branches/snapshot-2009.06.02-r5498/branch-info.txt Wed Jun 3 16:36:00 2009 @@ -7,3 +7,7 @@ svn copy -r5498 https://google-web-toolkit.googlecode.com/svn/trunk https://google-web-toolkit.googlecode.com/svn/branches/snapshot-2009.06.02-r5498 Merges: +/branches/snapshot-2009.05.12-r5406 r5488 was merged into this branch + (reversion of trunk r5293 and r5296) + svn merge -c5488 \ + https://google-web-toolkit.googlecode.com/svn/branches/snapshot-2009.05.12-r5406 . Modified: branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java ============================================================================== --- branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java (original) +++ branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java Wed Jun 3 16:36:00 2009 @@ -35,7 +35,7 @@ * the code that should be run when the application starts up. The remaining * elements are loaded via * {...@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback) - * GWT.runAsync}. See {...@link com.google.gwt.core.client.impl.AsyncFragmentLoader + * GWT.runAsync}. See {...@link com.google.gwt.core.client.AsyncFragmentLoader * AsyncFragmentLoader} for details on the necessary linker support for * runAsync. */ Modified: branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/Link.java ============================================================================== --- branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/Link.java (original) +++ branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/Link.java Wed Jun 3 16:36:00 2009 @@ -261,8 +261,12 @@ } /** - * Logs the total script size for this permutation, as calculated by - * {...@link CodeSplitter#totalScriptSize(int[])}. + * <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) { @@ -270,18 +274,53 @@ return; } + /* + * 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; - int[] jsLengths = new int[javaScript.length]; - for (int i = 0; i < javaScript.length; i++) { - jsLengths[i] = javaScript[i].length(); - } + 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; - int totalSize = CodeSplitter.totalScriptSize(jsLengths); + 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.TRACE, "Permutation " + permId + " (strong name " + compilation.getStrongName() + ") has an initial download size of " - + javaScript[0].length() + " and total script size of " + totalSize); + + javaScript[0].length() + " and max total script size of " + + maxTotalSize); } private final LinkOptionsImpl options; Modified: branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java ============================================================================== --- branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java (original) +++ branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Wed Jun 3 16:36:00 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; @@ -214,13 +213,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 +285,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. Modified: branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java ============================================================================== --- branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java (original) +++ branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java Wed Jun 3 16:36:00 2009 @@ -74,7 +74,7 @@ "com.google.gwt.core.client.JavaScriptObject", "com.google.gwt.lang.ClassLiteralHolder", "com.google.gwt.core.client.RunAsyncCallback", - "com.google.gwt.core.client.impl.AsyncFragmentLoader", + "com.google.gwt.core.client.AsyncFragmentLoader", "com.google.gwt.lang.EntryMethodHolder",})); static final Map<String, Set<String>> traceMethods = new HashMap<String, Set<String>>(); Modified: branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java ============================================================================== --- branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java (original) +++ branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java Wed Jun 3 16:36:00 2009 @@ -16,16 +16,13 @@ package com.google.gwt.dev.jjs.impl; import com.google.gwt.core.ext.TreeLogger; -import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JMethod; -import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JNode; -import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JStringLiteral; @@ -43,18 +40,15 @@ import com.google.gwt.dev.js.ast.JsVars; import com.google.gwt.dev.js.ast.JsVars.JsVar; import com.google.gwt.dev.util.PerfLogger; -import com.google.gwt.dev.util.collect.HashMap; -import com.google.gwt.dev.util.collect.HashSet; import java.util.ArrayList; -import java.util.LinkedHashSet; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * <p> @@ -63,37 +57,35 @@ * anything called in a callback supplied to * {...@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback) * GWT.runAsync()}. The remaining code should be downloadable via - * {...@link com.google.gwt.core.client.impl.AsyncFragmentLoader#inject(int)}. + * {...@link com.google.gwt.core.client.AsyncFragmentLoader#inject(int)}. * </p> * * <p> * The precise way the program is fragmented is an implementation detail that is * subject to change. Whenever the fragment strategy changes, * <code>AsyncFragmentLoader</code> must be updated in tandem. That said, the - * current fragmentation strategy is to create an initial fragment, a leftovers - * fragment, and one fragment per split point. Additionally, the splitter - * computes an initial load sequence. All runAsync calls in the initial load - * sequence are reached before any call not in the sequence. Further, any call - * in the sequence is reached before any call later in the sequence. + * current fragmentation strategy is to create an initial fragment and then + * three more fragments for each split point. For each split point, there is: * </p> * - * <p> - * The fragment for a split point contains different things depending on whether - * it is in the initial load sequence or not. If it's in the initial load - * sequence, then the fragment includes the code newly live once that split - * point is crossed, that wasn't already live for the set of split points - * earlier in the sequence. For a split point not in the initial load sequence, - * the fragment contains only code exclusive to that split point, that is, code - * that cannot be reached except via that split point. All other code goes into - * the leftovers fragment. - * </p> + * <ul> + * <li>a secondary base fragment, which is downloaded if this split point is the + * first one reached. It contains enough code to continue running as soon as it + * downloads. + * <li>an exclusively live fragment, which is downloaded if this split point is + * reached but is not the first one. It includes only that code that is + * exclusively needed by this split point. + * <li>a leftovers fragment, which includes all code that is in none of: the + * initial download, any exclusive fragment, or the secondary base fragment for + * this split point. + * </ul> */ public class CodeSplitter { /** * A statement logger that immediately prints out everything live that it * sees. */ - private class EchoStatementLogger implements StatementLogger { + public class EchoStatementLogger implements StatementLogger { public void logStatement(JsStatement stat, boolean isIncluded) { if (isIncluded) { if (stat instanceof JsExprStmt) { @@ -127,13 +119,10 @@ } /** - * A map from program atoms to the split point, if any, that they are - * exclusive to. Atoms not exclusive to any split point are either mapped to 0 - * or left out of the map entirely. Note that the map is incomplete; any entry - * not included has not been proven to be exclusive. Also, note that the - * initial load sequence is assumed to already be loaded. + * A map from program atoms to the fragment they should be placed in. An entry + * of 0 means it did not go into any fragment in particular. */ - private static class ExclusivityMap { + private static class FragmentMap { public Map<JField, Integer> fields = new HashMap<JField, Integer>(); public Map<JMethod, Integer> methods = new HashMap<JMethod, Integer>(); public Map<String, Integer> strings = new HashMap<String, Integer>(); @@ -141,15 +130,16 @@ } /** - * A liveness predicate that is based on an exclusivity map. + * A liveness predicate that is based on a fragment map. See + * {...@link #mapFragments()}. Note that all non-zero fragments are assumed to + * load after fragment 0, and so everything in fragment 0 is always live. */ - private static class ExclusivityMapLivenessPredicate implements + private static class FragmentMapLivenessPredicate implements LivenessPredicate { private final int fragment; - private final ExclusivityMap fragmentMap; + private final FragmentMap fragmentMap; - public ExclusivityMapLivenessPredicate(ExclusivityMap fragmentMap, - int fragment) { + public FragmentMapLivenessPredicate(FragmentMap fragmentMap, int fragment) { this.fragmentMap = fragmentMap; this.fragment = fragment; } @@ -185,11 +175,46 @@ } } - private static final Pattern LOADER_CLASS_PATTERN = Pattern.compile(FragmentLoaderCreator.ASYNC_LOADER_PACKAGE - + "." + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX + "([0-9]+)"); + /** + * A liveness predicate that checks two separate underlying predicates. + */ + private static class UnionLivenessPredicate implements LivenessPredicate { + private final LivenessPredicate pred1; + private final LivenessPredicate pred2; + + public UnionLivenessPredicate(LivenessPredicate pred1, + LivenessPredicate pred2) { + this.pred1 = pred1; + this.pred2 = pred2; + } + + public boolean isLive(JField field) { + return pred1.isLive(field) || pred2.isLive(field); + } + + public boolean isLive(JMethod method) { + return pred1.isLive(method) || pred2.isLive(method); + } + + public boolean isLive(JReferenceType type) { + return pred1.isLive(type) || pred2.isLive(type); + } + + public boolean isLive(String literal) { + return pred1.isLive(literal) || pred2.isLive(literal); + } + + public boolean miscellaneousStatementsAreLive() { + return pred1.miscellaneousStatementsAreLive() + || pred2.miscellaneousStatementsAreLive(); + } + } /** * A Java property that causes the fragment map to be logged. + * + * TODO(spoon) save the logging data to an auxiliary compiler output and, if + * the logging is not too slow, always enable it. */ private static String PROP_LOG_FRAGMENT_MAP = "gwt.jjs.logFragmentMap"; @@ -204,14 +229,17 @@ } 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 getBaseFragmentNumber(int sp, int numSplitPoints) { + return numSplitPoints + 2 * sp - 1; } public static int getExclusiveFragmentNumber(int splitPoint, @@ -219,95 +247,17 @@ return splitPoint; } - public static int getLeftoversFragmentNumber(int numSplitPoints) { - return numSplitPoints + 1; + 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 != 2); - - if (codeFragments == 1) { - return 0; - } - - return codeFragments - 2; - } - - /** - * Choose an initial load sequence of split points for the specified program. - * Do so by identifying split points whose code always load first, before any - * other split points. As a side effect, modifies - * {...@link com.google.gwt.core.client.impl.AsyncFragmentLoader#initialLoadSequence} - * in the program being compiled. - */ - 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); - - ControlFlowAnalyzer cfa = computeInitiallyLive(program); - - while (true) { - Set<Integer> nextSplitPoints = splitPointsReachable(cfa, - splitPointToMethod, numSplitPoints); - nextSplitPoints.removeAll(initialLoadSequence); - - if (nextSplitPoints.size() != 1) { - break; - } - - int nextSplitPoint = nextSplitPoints.iterator().next(); - initialLoadSequence.add(nextSplitPoint); - CodeSplitter.traverseEntry(program, cfa, nextSplitPoint); - } - - logInitialLoadSequence(logger, initialLoadSequence); - installInitialLoadSequenceField(program, initialLoadSequence); - } - - return initialLoadSequence; - } - - /** - * <p> - * Computes the "maximum total script size" for one 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> - * - * @param jsLengths The lengths of the fragments for the compilation of one - * permutation - */ - public static int totalScriptSize(int[] jsLengths) { - /* - * The total script size is currently simple: it's the sum of all the - * individual script files. - */ - - int maxTotalSize; - int numSplitPoints = numSplitPointsForFragments(jsLengths.length); - if (numSplitPoints == 0) { - maxTotalSize = jsLengths[0]; - } else { - // Add up the initial and exclusive fragments - maxTotalSize = jsLengths[0]; - for (int sp = 1; sp <= numSplitPoints; sp++) { - int excl = getExclusiveFragmentNumber(sp, numSplitPoints); - maxTotalSize += jsLengths[excl]; - } - - // Add the leftovers - maxTotalSize += jsLengths[getLeftoversFragmentNumber(numSplitPoints)]; - } - return maxTotalSize; + assert (((codeFragments - 1) % 3) == 0); + return (codeFragments - 1) / 3; } private static Map<JField, JClassLiteral> buildFieldToClassLiteralMap( @@ -323,33 +273,6 @@ return map; } - /** - * 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); - } - } - return splitPointToLoadMethod; - } - private static String fullNameString(JField field) { return field.getEnclosingType().getName() + "." + field.getName(); } @@ -364,65 +287,6 @@ return (value == null) ? 0 : value; } - private static void installInitialLoadSequenceField(JProgram program, - LinkedHashSet<Integer> initialLoadSequence) { - JField initLoadSeqField = program.getIndexedField("AsyncFragmentLoader.initialLoadSequence"); - SourceInfo info = program.createSourceInfoSynthetic(ReplaceRunAsyncs.class, - "array with initial load sequence"); - List<JExpression> intExprs = new ArrayList<JExpression>(); - for (int sp : initialLoadSequence) { - intExprs.add(program.getLiteralInt(sp)); - } - /* - * Note: the following field is known to have a manually installed - * initializer, of new int[0]. - */ - initLoadSeqField.getDeclarationStatement().initializer = JNewArray.createInitializers( - program, info, program.getTypeArray(JPrimitiveType.INT, 1), intExprs); - } - - private static void logInitialLoadSequence(TreeLogger logger, - LinkedHashSet<Integer> initialLoadSequence) { - StringBuffer message = new StringBuffer(); - message.append("Initial load sequence of split points: "); - if (initialLoadSequence.isEmpty()) { - message.append("(none)"); - } else { - boolean first = true; - for (int sp : initialLoadSequence) { - if (first) { - first = false; - } else { - message.append(", "); - } - message.append(sp); - } - } - - logger.log(TreeLogger.TRACE, message.toString()); - } - - /** - * 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 - */ - 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); - } - } - - return nextSplitPoints; - } - /** * Traverse all code in the program that is reachable via split point * <code>splitPoint</code>. @@ -467,7 +331,6 @@ private final Map<JField, JClassLiteral> fieldToLiteralOfClass; private final FragmentExtractor fragmentExtractor; - private final LinkedHashSet<Integer> initialLoadSequence; /** * Code that is initially live when the program first downloads. @@ -475,12 +338,6 @@ private final ControlFlowAnalyzer initiallyLive; private JProgram jprogram; private JsProgram jsprogram; - - /** - * Computed during {...@link #execImpl()}, so that intermediate steps of it can - * be used as they are created. - */ - private ControlFlowAnalyzer liveAfterInitialSequence; private final TreeLogger logger; private final boolean logging; private JavaToJavaScriptMap map; @@ -488,14 +345,12 @@ 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; numEntries = jprogram.entryMethods.size(); logging = Boolean.getBoolean(PROP_LOG_FRAGMENT_MAP); @@ -508,21 +363,18 @@ } /** - * Create a new fragment and add it to the table of fragments. + * Create a new fragment and add it to the list. * - * @param splitPoint The split point to associate this code with * @param alreadyLoaded The code that should be assumed to have already been * loaded - * @param liveNow The code that is assumed live once this fragment loads; - * anything in here but not in <code>alreadyLoaded</code> will be - * included in the created fragment - * @param stmtsToAppend Additional statements to append to the end of the new + * @param liveNow The code that needs to be live once this fragment loads + * @param statsToAppend Additional statements to append to the end of the new * fragment * @param fragmentStats The list of fragments to append to */ - private void addFragment(int splitPoint, LivenessPredicate alreadyLoaded, - LivenessPredicate liveNow, List<JsStatement> stmtsToAppend, - Map<Integer, List<JsStatement>> fragmentStats) { + private void addFragment(LivenessPredicate alreadyLoaded, + LivenessPredicate liveNow, List<JsStatement> statsToAppend, + List<List<JsStatement>> fragmentStats) { if (logging) { System.out.println(); System.out.println("==== Fragment " + fragmentStats.size() + " ===="); @@ -530,26 +382,20 @@ } List<JsStatement> stats = fragmentExtractor.extractStatements(liveNow, alreadyLoaded); - stats.addAll(stmtsToAppend); - fragmentStats.put(splitPoint, stats); + stats.addAll(statsToAppend); + fragmentStats.add(stats); } /** - * For each split point other than those in the initial load sequence, compute - * a CFA that traces every other split point. For those that are in the - * initial load sequence, add a <code>null</code> to the list. + * For each split point other than the initial one (0), compute a CFA that + * traces every other split point. */ private List<ControlFlowAnalyzer> computeAllButOneCfas() { List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>( numEntries - 1); for (int entry = 1; entry < numEntries; entry++) { - if (isInitial(entry)) { - allButOnes.add(null); - continue; - } - ControlFlowAnalyzer cfa = new ControlFlowAnalyzer( - liveAfterInitialSequence); + ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(initiallyLive); traverseAllButEntry(cfa, entry); // Traverse leftoversFragmentHasLoaded, because it should not // go into any of the exclusive fragments. @@ -572,23 +418,13 @@ return everything; } - /** - * Map each program atom as exclusive to some split point, whenever possible. - * Also fixes up load order problems that could result from splitting code - * based on this assumption. - */ - private ExclusivityMap determineExclusivity() { - ExclusivityMap fragmentMap = new ExclusivityMap(); - - mapExclusiveAtoms(fragmentMap); - fixUpLoadOrderDependencies(fragmentMap); - - return fragmentMap; - } - private void execImpl() { PerfLogger.start("CodeSplitter"); - Map<Integer, List<JsStatement>> fragmentStats = new HashMap<Integer, List<JsStatement>>(); + + FragmentMap fragmentMap = mapFragments(); + + List<List<JsStatement>> fragmentStats = new ArrayList<List<JsStatement>>( + 3 * numEntries - 2); { /* @@ -598,59 +434,46 @@ LivenessPredicate alreadyLoaded = new NothingAlivePredicate(); LivenessPredicate liveNow = new CfaLivenessPredicate(initiallyLive); List<JsStatement> noStats = new ArrayList<JsStatement>(); - addFragment(0, alreadyLoaded, liveNow, noStats, fragmentStats); + addFragment(alreadyLoaded, liveNow, noStats, fragmentStats); } /* - * Compute the base fragments, for split points in the initial load - * sequence. - */ - liveAfterInitialSequence = new ControlFlowAnalyzer(initiallyLive); - for (int sp : initialLoadSequence) { - LivenessPredicate alreadyLoaded = new CfaLivenessPredicate( - liveAfterInitialSequence); - - ControlFlowAnalyzer liveAfterSp = new ControlFlowAnalyzer( - liveAfterInitialSequence); - traverseEntry(liveAfterSp, sp); - LivenessPredicate liveNow = new CfaLivenessPredicate(liveAfterSp); - - List<JsStatement> statsToAppend = fragmentExtractor.createCallsToEntryMethods(sp); - - addFragment(sp, alreadyLoaded, liveNow, statsToAppend, fragmentStats); - - liveAfterInitialSequence = liveAfterSp; - } - - ExclusivityMap fragmentMap = determineExclusivity(); - - /* * Compute the exclusively live fragments. Each includes everything * exclusively live after entry point i. */ for (int i = 1; i < numEntries; i++) { - if (isInitial(i)) { - continue; - } - LivenessPredicate alreadyLoaded = new ExclusivityMapLivenessPredicate( + LivenessPredicate alreadyLoaded = new FragmentMapLivenessPredicate( fragmentMap, 0); - LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate( - fragmentMap, i); + LivenessPredicate liveNow = new FragmentMapLivenessPredicate(fragmentMap, + i); List<JsStatement> statsToAppend = fragmentExtractor.createCallsToEntryMethods(i); - addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats); + addFragment(alreadyLoaded, liveNow, statsToAppend, fragmentStats); } /* - * Compute the leftovers fragment. + * Add secondary base fragments and their associated leftover fragments. */ - { - LivenessPredicate alreadyLoaded = new CfaLivenessPredicate( - liveAfterInitialSequence); - LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate( + for (int base = 1; base < numEntries; base++) { + ControlFlowAnalyzer baseCfa = new ControlFlowAnalyzer(initiallyLive); + traverseEntry(baseCfa, base); + LivenessPredicate baseLive = new CfaLivenessPredicate(baseCfa); + + // secondary base + List<JsStatement> baseStatsToAppend = fragmentExtractor.createCallsToEntryMethods(base); + addFragment(new CfaLivenessPredicate(initiallyLive), baseLive, + baseStatsToAppend, fragmentStats); + + // leftovers + LivenessPredicate globalLeftoversLive = new FragmentMapLivenessPredicate( fragmentMap, 0); + LivenessPredicate associatedExclusives = new FragmentMapLivenessPredicate( + fragmentMap, base); + // Be sure to add in anything in the exclusives for base that is + // not in its secondary base. + LivenessPredicate leftoversLive = new UnionLivenessPredicate( + globalLeftoversLive, associatedExclusives); List<JsStatement> statsToAppend = fragmentExtractor.createCallToLeftoversFragmentHasLoaded(); - addFragment(numEntries, alreadyLoaded, liveNow, statsToAppend, - fragmentStats); + addFragment(baseLive, leftoversLive, statsToAppend, fragmentStats); } // now install the new statements in the program fragments @@ -680,7 +503,7 @@ * happen, so it seems better to keep the compiler simpler. * </p> */ - private void fixUpLoadOrderDependencies(ExclusivityMap fragmentMap) { + private void fixUpLoadOrderDependencies(FragmentMap fragmentMap) { fixUpLoadOrderDependenciesForMethods(fragmentMap); fixUpLoadOrderDependenciesForTypes(fragmentMap); fixUpLoadOrderDependenciesForClassLiterals(fragmentMap); @@ -688,7 +511,7 @@ } private void fixUpLoadOrderDependenciesForClassLiterals( - ExclusivityMap fragmentMap) { + FragmentMap fragmentMap) { int numClassLitStrings = 0; int numFixups = 0; for (JField field : fragmentMap.fields.keySet()) { @@ -712,7 +535,7 @@ } private void fixUpLoadOrderDependenciesForFieldsInitializedToStrings( - ExclusivityMap fragmentMap) { + FragmentMap fragmentMap) { int numFixups = 0; int numFieldStrings = 0; @@ -736,7 +559,7 @@ + +numFieldStrings); } - private void fixUpLoadOrderDependenciesForMethods(ExclusivityMap fragmentMap) { + private void fixUpLoadOrderDependenciesForMethods(FragmentMap fragmentMap) { int numFixups = 0; for (JDeclaredType type : jprogram.getDeclaredTypes()) { @@ -766,7 +589,7 @@ + jprogram.getDeclaredTypes().size()); } - private void fixUpLoadOrderDependenciesForTypes(ExclusivityMap fragmentMap) { + private void fixUpLoadOrderDependenciesForTypes(FragmentMap fragmentMap) { int numFixups = 0; Queue<JReferenceType> typesToCheck = new ArrayBlockingQueue<JReferenceType>( jprogram.getDeclaredTypes().size()); @@ -791,16 +614,12 @@ + jprogram.getDeclaredTypes().size()); } - private boolean isInitial(int entry) { - return initialLoadSequence.contains(entry); - } - /** - * Map atoms to exclusive fragments. Do this by trying to find code atoms that - * are only needed by a single split point. Such code can be moved to the - * exclusively live fragment associated with that split point. + * Map code to fragments. Do this by trying to find code atoms that are only + * needed by a single split point. Such code can be moved to the exclusively + * live fragment associated with that split point. */ - private void mapExclusiveAtoms(ExclusivityMap fragmentMap) { + private void mapExclusiveAtoms(FragmentMap fragmentMap) { List<ControlFlowAnalyzer> allButOnes = computeAllButOneCfas(); ControlFlowAnalyzer everything = computeCompleteCfa(); @@ -819,9 +638,6 @@ allFields.addAll(everything.getFieldsWritten()); for (int entry = 1; entry < numEntries; entry++) { - if (isInitial(entry)) { - continue; - } ControlFlowAnalyzer allButOne = allButOnes.get(entry - 1); Set<JNode> allLiveNodes = union(allButOne.getLiveFieldsAndMethods(), allButOne.getFieldsWritten()); @@ -833,6 +649,21 @@ updateMap(entry, fragmentMap.types, allButOne.getInstantiatedTypes(), everything.getInstantiatedTypes()); } + } + + /** + * Map each program atom to a fragment. Atoms are mapped to a non-zero + * fragment whenever they are known not to be needed whenever that fragment's + * split point has not been reached. Any atoms that cannot be so mapped are + * left in fragment zero. + */ + private FragmentMap mapFragments() { + FragmentMap fragmentMap = new FragmentMap(); + + mapExclusiveAtoms(fragmentMap); + fixUpLoadOrderDependencies(fragmentMap); + + return fragmentMap; } /** Modified: branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java ============================================================================== --- branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java (original) +++ branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java Wed Jun 3 16:36:00 2009 @@ -204,7 +204,7 @@ /** * Create a call to - * {...@link com.google.gwt.core.client.impl.AsyncFragmentLoader#leftoversFragmentHasLoaded()}. + * {...@link com.google.gwt.lang.AsyncFragmentLoader#leftoversFragmentHasLoaded()}. */ public List<JsStatement> createCallToLeftoversFragmentHasLoaded() { JMethod loadedMethod = jprogram.getIndexedMethod("AsyncFragmentLoader.leftoversFragmentHasLoaded"); Modified: branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java ============================================================================== --- branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java (original) +++ branches/snapshot-2009.06.02-r5498/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java Wed Jun 3 16:36:00 2009 @@ -37,10 +37,9 @@ * runAsync and how it works. */ public class FragmentLoaderCreator { - public static final String ASYNC_FRAGMENT_LOADER = "com.google.gwt.core.client.impl.AsyncFragmentLoader"; + public static final String ASYNC_FRAGMENT_LOADER = "com.google.gwt.core.client.AsyncFragmentLoader"; public static final String ASYNC_LOADER_CLASS_PREFIX = "AsyncLoader"; public static final String ASYNC_LOADER_PACKAGE = "com.google.gwt.lang.asyncloaders"; - public static final String LOADER_METHOD_RUN_ASYNC = "runAsync"; public static final String RUN_ASYNC_CALLBACK = "com.google.gwt.core.client.RunAsyncCallback"; private static final String GWT_CLASS = FindDeferredBindingSitesVisitor.MAGIC_CLASS; private static final String PROP_RUN_ASYNC_NEVER_RUNS = "gwt.jjs.runAsyncNeverRuns"; @@ -139,8 +138,7 @@ * <code>GWT.runAsync</code> are replaced by calls to this method. */ private void generateRunAsyncMethod(PrintWriter srcWriter) { - srcWriter.println("public static void " + LOADER_METHOD_RUN_ASYNC - + "(RunAsyncCallback callback) {"); + srcWriter.println("public static void runAsync(RunAsyncCallback callback) {"); srcWriter.println(getCallbackListSimpleName() + " newCallback = new " + getCallbackListSimpleName() + "();"); srcWriter.println("newCallback.callback = callback;"); --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
