Working CodeSplitter2. -With this the people should be able to test the new CodeSplitter. -It is protected by a -XfragmentMerge flag, it will not break anyone. -While it is working in lots of internal projects, there are still room for improvement. There are enough todo's in places where work is needed. Review at http://gwt-code-reviews.appspot.com/1631803 git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10866 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 4d033ed..d00aa4e 100644 --- a/dev/core/src/com/google/gwt/dev/Link.java +++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -503,7 +503,7 @@ /** * Logs the total script size for this permutation, as calculated by - * {@link CodeSplitter#totalScriptSize(int[])}. + * {@link CodeSplitter2#totalScriptSize(int[])}. */ private static void logScriptSize(TreeLogger logger, int permId, StandardCompilationResult compilation) { @@ -518,6 +518,7 @@ jsLengths[i] = javaScript[i].length(); } + // TODO(acleung): This is broken for CodeSplitter2. int totalSize = CodeSplitter.totalScriptSize(jsLengths); if (logger.isLoggable(TreeLogger.TRACE)) {
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java index 5f13163..770a6f5 100644 --- a/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java +++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskArgProcessor.java
@@ -29,6 +29,7 @@ import com.google.gwt.dev.util.arg.ArgHandlerDumpSignatures; import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions; import com.google.gwt.dev.util.arg.ArgHandlerEnableClosureCompiler; +import com.google.gwt.dev.util.arg.ArgHandlerFragmentMerge; import com.google.gwt.dev.util.arg.ArgHandlerGenDir; import com.google.gwt.dev.util.arg.ArgHandlerMaxPermsPerPrecompile; import com.google.gwt.dev.util.arg.ArgHandlerOptimize; @@ -62,6 +63,7 @@ registerHandler(new ArgHandlerCompilerMetrics(options)); registerHandler(new ArgHandlerDisableSoycHtml(options)); registerHandler(new ArgHandlerEnableClosureCompiler(options)); + registerHandler(new ArgHandlerFragmentMerge(options)); } @Override
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java index 1fea930..1a3ab05 100644 --- a/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java +++ b/dev/core/src/com/google/gwt/dev/PrecompileTaskOptionsImpl.java
@@ -49,6 +49,11 @@ } @Override + public int getFragmentsMerge() { + return jjsOptions.getFragmentsMerge(); + } + + @Override public File getGenDir() { return genDir; } @@ -188,6 +193,11 @@ } @Override + public void setFragmentsMerge(int numFragments) { + jjsOptions.setFragmentsMerge(numFragments); + } + + @Override public void setGenDir(File genDir) { this.genDir = genDir; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java index 3162e5e..18fa84a 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -20,6 +20,7 @@ import com.google.gwt.dev.util.arg.OptionDisableClassMetadata; import com.google.gwt.dev.util.arg.OptionEnableAssertions; import com.google.gwt.dev.util.arg.OptionEnableClosureCompiler; +import com.google.gwt.dev.util.arg.OptionFragmentsMerge; import com.google.gwt.dev.util.arg.OptionOptimize; import com.google.gwt.dev.util.arg.OptionOptimizePrecompile; import com.google.gwt.dev.util.arg.OptionRunAsyncEnabled; @@ -36,6 +37,6 @@ OptionDisableClassMetadata, OptionDisableCastChecking, OptionEnableAssertions, OptionRunAsyncEnabled, OptionScriptStyle, OptionSoycEnabled, OptionSoycDetailed, OptionOptimizePrecompile, OptionStrict, OptionSoycHtmlDisabled, - OptionEnableClosureCompiler { + OptionEnableClosureCompiler, OptionFragmentsMerge { }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java index c1695bb..173df6f 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -38,6 +38,7 @@ private boolean soycHtmlDisabled = false; private boolean strict = false; private boolean closureCompilerEnabled; + private int fragmentsMerge = -1; public JJSOptionsImpl() { } @@ -60,8 +61,14 @@ setSoycHtmlDisabled(other.isSoycHtmlDisabled()); setStrict(other.isStrict()); setClosureCompilerEnabled(other.isClosureCompilerEnabled()); + setFragmentsMerge(other.getFragmentsMerge()); } - + + @Override + public int getFragmentsMerge() { + return fragmentsMerge; + } + @Override public int getOptimizationLevel() { return optimizationLevel; @@ -167,6 +174,11 @@ } @Override + public void setFragmentsMerge(int numFragments) { + this.fragmentsMerge = numFragments; + } + + @Override public void setOptimizationLevel(int level) { optimizationLevel = level; } @@ -205,4 +217,5 @@ public void setStrict(boolean strict) { this.strict = strict; } + }
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 9a028a8..5f3c93d 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -67,8 +67,9 @@ import com.google.gwt.dev.jjs.impl.AstDumper; import com.google.gwt.dev.jjs.impl.CastNormalizer; import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer; -import com.google.gwt.dev.jjs.impl.CodeSplitter; import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder; +import com.google.gwt.dev.jjs.impl.CodeSplitter; +import com.google.gwt.dev.jjs.impl.CodeSplitter2; import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer; import com.google.gwt.dev.jjs.impl.DeadCodeElimination; import com.google.gwt.dev.jjs.impl.EnumOrdinalizer; @@ -348,10 +349,21 @@ // (10) Split up the program into fragments SyntheticArtifact dependencies = null; + if (options.isRunAsyncEnabled()) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, chooseDependencyRecorder(options - .isSoycEnabled(), baos)); + int fragmentsMerge = options.getFragmentsMerge(); + + // Pick and choose which code splitter to use. Only use the experimental + // one when the user explicitly decides the project needs fragment + // merging. + if (fragmentsMerge > 0) { + CodeSplitter2.exec(logger, jprogram, jsProgram, jjsmap, fragmentsMerge, + chooseDependencyRecorder(options.isSoycEnabled(), baos)); + } else { + CodeSplitter.exec(logger, jprogram, jsProgram, jjsmap, chooseDependencyRecorder(options + .isSoycEnabled(), baos)); + } if (baos.size() == 0 && options.isSoycEnabled()) { recordNonSplitDependencies(jprogram, baos); } @@ -618,7 +630,7 @@ // Fix up GWT.runAsync() if (module != null && options.isRunAsyncEnabled()) { ReplaceRunAsyncs.exec(logger, jprogram); - CodeSplitter.pickInitialLoadSequence(logger, jprogram, module.getProperties()); + CodeSplitter2.pickInitialLoadSequence(logger, jprogram, module.getProperties()); } ImplementClassLiteralsAsFields.exec(jprogram);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNumericEntry.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNumericEntry.java new file mode 100644 index 0000000..77e8c57 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNumericEntry.java
@@ -0,0 +1,68 @@ +/* + * Copyright 2011 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.ast; + +import com.google.gwt.dev.jjs.SourceInfo; + +/** + * A place order a numeric value in the AST. + * + * It temporary holds an numeric value during compilation. In a later stage, + * a compiler pass easily replace these place holder. For example, + * {@link ReplaceRunAsyncs would } + */ +public final class JNumericEntry extends JExpression { + + private final String key; + private int value; + + public JNumericEntry(SourceInfo info, String key, int value) { + super(info); + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + @Override + public JType getType() { + return JPrimitiveType.INT; + } + + public int getValue() { + return value; + } + + @Override + public boolean hasSideEffects() { + return false; + } + + public void setValue(int value) { + this.value = value; + } + + @Override + public void traverse(JVisitor visitor, Context ctx) { + if (visitor.visit(this, ctx)) { + } + visitor.endVisit(this, ctx); + } + +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java index 95aba96..18835af 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -21,6 +21,7 @@ import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.jjs.ast.js.JsCastMap; import com.google.gwt.dev.jjs.impl.CodeSplitter; +import com.google.gwt.dev.jjs.impl.CodeSplitter2.FragmentPartitioningResult; import com.google.gwt.dev.util.collect.Lists; import java.io.IOException; @@ -45,6 +46,7 @@ * Root for the AST representing an entire Java program. */ public class JProgram extends JNode { + private static final class ArrayTypeComparator implements Comparator<JArrayType>, Serializable { public int compare(JArrayType o1, JArrayType o2) { int comp = o1.getDims() - o2.getDims(); @@ -244,18 +246,29 @@ * be preferred whenever a JProgram instance is available. * * @param initialSeq The initial split point sequence of the program + * @param result The fragment partitioning result, null if it hasn't be partitioned. * @param numSps The number of split points in the program * @param firstFragment The first fragment to consider * @param restFragments The rest of the fragments to consider */ - public static int lastFragmentLoadingBefore(List<Integer> initialSeq, int numSps, - int firstFragment, int... restFragments) { + public static int lastFragmentLoadingBefore(List<Integer> initialSeq, + FragmentPartitioningResult result, int numSps, int firstFragment, int... restFragments) { int latest = firstFragment; for (int frag : restFragments) { - latest = pairwiseLastFragmentLoadingBefore(initialSeq, numSps, latest, frag); + latest = pairwiseLastFragmentLoadingBefore(initialSeq, result, numSps, latest, frag); } return latest; } + + public static int lastFragmentLoadingBefore(List<Integer> initialSeq, + int numSps, int firstFragment, int... restFragments) { + int latest = firstFragment; + for (int frag : restFragments) { + latest = pairwiseLastFragmentLoadingBefore(initialSeq, null, numSps, latest, frag); + } + return latest; + } + public static void serializeTypes(List<JDeclaredType> types, ObjectOutputStream stream) throws IOException { @@ -272,8 +285,9 @@ * The main logic behind {@link #lastFragmentLoadingBefore(int, int...)} and * {@link #lastFragmentLoadingBefore(List, int, int, int...)}. */ - private static int pairwiseLastFragmentLoadingBefore(List<Integer> initialSeq, int numSps, - int frag1, int frag2) { + private static int pairwiseLastFragmentLoadingBefore(List<Integer> initialSeq, + FragmentPartitioningResult result, int numSps, int frag1, int frag2) { + if (frag1 == frag2) { return frag1; } @@ -286,10 +300,22 @@ return 0; } - // See if either is in the initial sequence - int initPos1 = initialSeq.indexOf(frag1); - int initPos2 = initialSeq.indexOf(frag2); + // TODO(acleung): While the logic for this is correct, the terminology used + // in the code is not correct when fragment partitioning is on. + // Once the new code splitter is the default. This function needs to be + // rewritten. + int sp1 = frag1; + int sp2 = frag2; + // If there were some fragment merging. + if (result != null) { + sp1 = result.getSplitPointFromFragmnet(sp1); + sp2 = result.getSplitPointFromFragmnet(sp2); + } + + int initPos1 = initialSeq.indexOf(sp1); + int initPos2 = initialSeq.indexOf(sp2); + // If both are in the initial sequence, then pick the earlier if (initPos1 >= 0 && initPos2 >= 0) { if (initPos1 < initPos2) { @@ -309,8 +335,11 @@ assert (initPos1 < 0 && initPos2 < 0); assert (frag1 != frag2); - // They are both leftovers or exclusive. Leftovers goes first in all cases. - return CodeSplitter.getLeftoversFragmentNumber(numSps); + if (result != null) { + return result.getLeftoverFragmentIndex(); + } else { + return CodeSplitter.getLeftoversFragmentNumber(numSps); + } } public final List<JClassType> codeGenTypes = new ArrayList<JClassType>(); @@ -369,6 +398,8 @@ private JClassType typeSpecialJavaScriptObject; private JClassType typeString; + + private FragmentPartitioningResult fragmentPartitioninResult; /** * Constructor. @@ -897,8 +928,8 @@ * supplied fragments, or it might be a common predecessor. */ public int lastFragmentLoadingBefore(int firstFragment, int... restFragments) { - return lastFragmentLoadingBefore(splitPointInitialSequence, runAsyncs.size(), firstFragment, - restFragments); + return lastFragmentLoadingBefore(splitPointInitialSequence, fragmentPartitioninResult, + runAsyncs.size(), firstFragment, restFragments); } public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) { @@ -932,6 +963,10 @@ } } + public void setFragmentPartitioningResult(FragmentPartitioningResult result) { + fragmentPartitioninResult = result; + } + public void setRunAsyncs(List<JRunAsync> runAsyncs) { this.runAsyncs = Lists.normalizeUnmodifiable(runAsyncs); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java index 8e17e97..3d332ff 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
@@ -376,6 +376,10 @@ public void endVisit(JNullType x, Context ctx) { endVisit((JReferenceType) x, ctx); } + + public void endVisit(JNumericEntry x, Context ctx) { + endVisit((JExpression) x, ctx); + } public void endVisit(JParameter x, Context ctx) { endVisit((JVariable) x, ctx); @@ -704,6 +708,10 @@ public boolean visit(JNullType x, Context ctx) { return visit((JReferenceType) x, ctx); } + + public boolean visit(JNumericEntry x, Context ctx) { + return visit((JExpression) x, ctx); + } public boolean visit(JParameter x, Context ctx) { return visit((JVariable) x, ctx);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java index 628841c..d1089da 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
@@ -41,6 +41,7 @@ import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JNewInstance; import com.google.gwt.dev.jjs.ast.JNullLiteral; +import com.google.gwt.dev.jjs.ast.JNumericEntry; import com.google.gwt.dev.jjs.ast.JParameterRef; import com.google.gwt.dev.jjs.ast.JPostfixOperation; import com.google.gwt.dev.jjs.ast.JPrefixOperation; @@ -266,6 +267,12 @@ } @Override + public boolean visit(JNumericEntry x, Context ctx) { + expression = new JNumericEntry(x.getSourceInfo(), x.getKey(), x.getValue()); + return false; + } + + @Override public boolean visit(JNullLiteral x, Context ctx) { expression = x; return false;
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 4e317f3..9d9e82b 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
@@ -28,22 +28,25 @@ 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.JIntLiteral; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JNode; +import com.google.gwt.dev.jjs.ast.JNumericEntry; 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.JRunAsync; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JVisitor; -import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer.DependencyRecorder; +import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder; 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.ast.JsBlock; +import com.google.gwt.dev.js.ast.JsContext; +import com.google.gwt.dev.js.ast.JsModVisitor; +import com.google.gwt.dev.js.ast.JsNumericEntry; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.util.JsniRef; @@ -52,14 +55,12 @@ import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; -import com.google.gwt.thirdparty.guava.common.base.Predicate; import java.util.ArrayList; import java.util.BitSet; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -81,40 +82,62 @@ * code. * * TODO(acleung): Rename to CodeSplitter upon completion. + * TODO(acleung): Some of the data structures and methods are EXACT copy of the + * original CoderSplitter.java. This is intentional as we are going to remove + * the old one upon completion of this one. * TODO(acleung): Figure out how to integrate with SOYC and dependency tracker. * TODO(acleung): Insert SpeedTracer calls at performance sensitive places. * TODO(acleung): Insert logger calls to generate meaningful logs. - * TODO(acleung): Modify the fragment loader. - * TODO(acleung): May be add back the odd heuristics if needed. + * TODO(acleung): May be add back the old heuristics if needed. */ public class CodeSplitter2 { - + /** - * A dependency recorder that can record multiple dependency graphs. It has - * methods for starting and finishing new dependency graphs. + * A read-only class that holds some information about the result of the + * partition process. * - * TODO(acleung): This is currently broken. I need to investiage more on how we inform SOYC - * the dependency results. + * Unlike the original code split where information about the fragments and + * be deduced from the JProgram, certain compiler passes needs to know what + * happened here in order to do their job correctly. */ - public interface MultipleDependencyGraphRecorder extends DependencyRecorder { + public static final class FragmentPartitioningResult { + private final int[] fragmentToSplitPoint; + + private FragmentPartitioningResult(int[] splitPointToFragmentMap, int numFragments) { + fragmentToSplitPoint = new int[numFragments]; + for (int i = 0, len = splitPointToFragmentMap.length - 1; i < len; i++) { + System.out.println("splitPointToFragmentMap[" + i + "] = " + splitPointToFragmentMap[i]); + } + for (int i = 1, len = splitPointToFragmentMap.length - 1; i < len; i++) { + if (fragmentToSplitPoint[splitPointToFragmentMap[i]] == 0) { + fragmentToSplitPoint[splitPointToFragmentMap[i]] = i; + } else { + fragmentToSplitPoint[splitPointToFragmentMap[i]] = -1; + } + } + } + /** - * Stop recording dependencies. + * @return Fragment number of the left over fragment. */ - void close(); + public int getLeftoverFragmentIndex() { + return getNumFragments() - 1; + } + + /** + * @return Number of code fragments in the compilation. Leftover fragment and initial fragment. + */ + public int getNumFragments() { + return fragmentToSplitPoint.length; + } /** - * Stop recording the current dependency graph. + * @return One of the split point number in a given fragment. If there + * are more than one splitpoints in the a fragment, -1 is returned. */ - void endDependencyGraph(); - - void open(); - - /** - * Start a new dependency graph. It can be an extension of a previously - * recorded dependency graph, in which case the dependencies in the previous - * graph will not be repeated. - */ - void startDependencyGraph(String name, String extnds); + public int getSplitPointFromFragmnet(int fragment) { + return fragmentToSplitPoint[fragment]; + } } /** @@ -133,6 +156,68 @@ */ EDGE_GREEDY, } + + /** + * 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. + */ + private static class ExclusivityMap { + 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>(); + public Map<JDeclaredType, Integer> types = new HashMap<JDeclaredType, Integer>(); + } + + /** + * A liveness predicate that is based on an exclusivity map. + */ + private static class ExclusivityMapLivenessPredicate implements LivenessPredicate { + private final int fragment; + private final ExclusivityMap fragmentMap; + + public ExclusivityMapLivenessPredicate(ExclusivityMap fragmentMap, int fragment) { + this.fragmentMap = fragmentMap; + this.fragment = fragment; + } + + @Override + public boolean isLive(JDeclaredType type) { + return checkMap(fragmentMap.types, type); + } + + @Override + public boolean isLive(JField field) { + return checkMap(fragmentMap.fields, field); + } + + @Override + public boolean isLive(JMethod method) { + return checkMap(fragmentMap.methods, method); + } + + @Override + public boolean isLive(String literal) { + return checkMap(fragmentMap.strings, literal); + } + + @Override + public boolean miscellaneousStatementsAreLive() { + return true; + } + + private <T> boolean checkMap(Map<T, Integer> map, T x) { + Integer entryForX = map.get(x); + if (entryForX == null) { + // unrecognized items are always live + return true; + } else { + return (fragment == entryForX) || (entryForX == 0); + } + } + } /** * Maps an atom to a set of split point that can be live (NOT necessary exclusively) @@ -180,87 +265,19 @@ } /** - * LivenessPredicate to tell the fragment extractor what is live. - */ - private static class LiveSplitPointsPredicate implements LivenessPredicate { - private final LiveSplitPointMap map; - private final Predicate<BitSet> mask; - LiveSplitPointsPredicate(LiveSplitPointMap map, Predicate<BitSet> mask) { - this.map = map; - this.mask = mask; - } - - @Override - public boolean isLive(JDeclaredType type) { - return isLive(map.types, type); - } - - @Override - public boolean isLive(JField field) { - return isLive(map.fields, field); - } - - @Override - public boolean isLive(JMethod method) { - return isLive(map.methods, method); - } - - @Override - public boolean isLive(String literal) { - return isLive(map.strings, literal); - } - - @Override - public boolean miscellaneousStatementsAreLive() { - return true; - } - - private <T> boolean isLive(Map<T, BitSet> map, T atom) { - BitSet value = map.get(atom); - return value != null && mask.apply(value); - } - } - - /** - * A {@link MultipleDependencyGraphRecorder} that does nothing. - */ - public static final MultipleDependencyGraphRecorder NULL_RECORDER = - new MultipleDependencyGraphRecorder() { - public void close() { - } - - public void endDependencyGraph() { - } - - public void methodIsLiveBecause(JMethod liveMethod, ArrayList<JMethod> dependencyChain) { - } - - public void open() { - } - - public void startDependencyGraph(String name, String extnds) { - } - }; - - /** - * Number of split points to merge, this should be configurable by user later. - */ - public static final int NUM_SPLITPOINTS_TO_MERGE = 12; - - /** * The property key for a list of initially loaded split points. */ private static final String PROP_INITIAL_SEQUENCE = "compiler.splitpoint.initial.sequence"; public static ControlFlowAnalyzer computeInitiallyLive(JProgram jprogram) { - return computeInitiallyLive(jprogram, NULL_RECORDER); + return computeInitiallyLive(jprogram, CodeSplitter.NULL_RECORDER); } - + public static ControlFlowAnalyzer computeInitiallyLive( JProgram jprogram, MultipleDependencyGraphRecorder dependencyRecorder) { - // Control Flow Analysis from a split point. - ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram); dependencyRecorder.startDependencyGraph("initial", null); + + ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(jprogram); cfa.setDependencyRecorder(dependencyRecorder); cfa.traverseEntryMethods(); traverseClassArray(jprogram, cfa); @@ -268,15 +285,17 @@ dependencyRecorder.endDependencyGraph(); return cfa; } - + public static void exec(TreeLogger logger, JProgram jprogram, JsProgram jsprogram, - JavaToJavaScriptMap map, MultipleDependencyGraphRecorder dependencyRecorder) { + JavaToJavaScriptMap map, int fragmentsToMerge, + MultipleDependencyGraphRecorder dependencyRecorder) { 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, dependencyRecorder).execImpl(); + new CodeSplitter2( + logger, jprogram, jsprogram, map, fragmentsToMerge, dependencyRecorder).execImpl(); codeSplitterEvent.end(); } @@ -298,8 +317,10 @@ throw new UnableToCompleteException(); } final String lookupErrorHolder[] = new String[1]; + @SuppressWarnings("deprecation") JNode referent = JsniRefLookup.findJsniRefTarget(jsniRef, program, new JsniRefLookup.ErrorReporter() { + @Override public void reportError(String error) { lookupErrorHolder[0] = error; } @@ -346,7 +367,7 @@ codeSplitterEvent.end(); return result; } - + /** * 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 @@ -356,6 +377,7 @@ * * @throws UnableToCompleteException If the module specifies a bad load order */ + @SuppressWarnings("javadoc") public static void pickInitialLoadSequence(TreeLogger logger, JProgram program, Properties properties) throws UnableToCompleteException { Event codeSplitterEvent = @@ -391,6 +413,7 @@ program.setSplitPointInitialSequence(new ArrayList<Integer>(initialLoadSequence)); codeSplitterEvent.end(); } + private static Map<JField, JClassLiteral> buildFieldToClassLiteralMap(JProgram jprogram) { final Map<JField, JClassLiteral> map = new HashMap<JField, JClassLiteral>(); class BuildFieldToLiteralVisitor extends JVisitor { @@ -402,6 +425,7 @@ (new BuildFieldToLiteralVisitor()).accept(jprogram); return map; } + private static <T> void countShardedAtomsOfType(Map<T, BitSet> livenessMap, int[][] matrix) { // Count the number of atoms shared only by for (Entry<T, BitSet> fieldLiveness : livenessMap.entrySet()) { @@ -419,7 +443,20 @@ matrix[start][end]++; } } - + + /** + * Extract the types from a set that happen to be declared types. + */ + private static Set<JDeclaredType> declaredTypesIn(Set<JReferenceType> types) { + Set<JDeclaredType> result = new HashSet<JDeclaredType>(); + for (JReferenceType type : types) { + if (type instanceof JDeclaredType) { + result.add((JDeclaredType) type); + } + } + return result; + } + private static <T> int getOrZero(Map<T, BitSet> map, T key) { BitSet value = map.get(key); if (value != null && value.cardinality() == 1) { @@ -427,7 +464,7 @@ } return 0; } - + /** * Installs the initial load sequence into AsyncFragmentLoader.BROWSER_LOADER. * The initializer looks like this: @@ -449,7 +486,7 @@ assert ((JNewArray) arg1).getArrayType() == arrayType; List<JExpression> initializers = new ArrayList<JExpression>(initialLoadSequence.size()); for (int sp : initialLoadSequence) { - initializers.add(JIntLiteral.get(sp)); + initializers.add(new JNumericEntry(call.getSourceInfo(), "RunAsyncFragmentIndex", sp)); } JNewArray newArray = JNewArray.createInitializers(arg1.getSourceInfo(), arrayType, Lists @@ -469,9 +506,7 @@ } for (JField node : cfa.getFieldsWritten()) { - if (node instanceof JField) { - liveness.setLive((JField) node, idx); - } + liveness.setLive(node, idx); } for (String s : cfa.getLiveStrings()) { @@ -522,7 +557,7 @@ } } } - + /** * Any immortal codegen types must be part of the initial download. */ @@ -538,8 +573,33 @@ } } - private final Map<JField, JClassLiteral> fieldToLiteralOfClass; + private static <T> Set<T> union(Set<? extends T> set1, Set<? extends T> set2) { + Set<T> union = new HashSet<T>(); + union.addAll(set1); + union.addAll(set2); + return union; + } + private static <T> void updateReverseMap(int entry, Map<T, Integer> map, Set<?> liveWithoutEntry, + Iterable<T> all) { + for (T each : all) { + if (!liveWithoutEntry.contains(each)) { + /* + * Note that it is fine to overwrite a preexisting entry in the map. If + * an atom is dead until split point i has been reached, and is also + * dead until entry j has been reached, then it is dead until both have + * been reached. Thus, it can be downloaded along with either i's or j's + * code. + */ + map.put(each, entry); + } + } + } + + ExclusivityMap fragmentMap = new ExclusivityMap(); + + private final Map<JField, JClassLiteral> fieldToLiteralOfClass; + private FragmentExtractor fragmentExtractor; /** @@ -561,6 +621,17 @@ private final Set<JMethod> methodsInJavaScript; /** + * Number of split points to merge. + */ + private final int splitPointsMerge; + + /** + * Maps the split point index X to Y where where that split point X would + * appear in the Y.cache.js + */ + private final int[] splitPointToCodeIndexMap; + + /** * Maps a split-point number to a fragment number. * * splitPointToFragmmentMap[x] = y implies split point #x is in fragment #y. @@ -571,9 +642,11 @@ private final int[] splitPointToFragmentMap; private CodeSplitter2(TreeLogger logger, JProgram jprogram, JsProgram jsprogram, - JavaToJavaScriptMap map, MultipleDependencyGraphRecorder dependencyRecorder) { + JavaToJavaScriptMap map, int splitPointsMerge, + MultipleDependencyGraphRecorder dependencyRecorder) { this.jprogram = jprogram; this.jsprogram = jsprogram; + this.splitPointsMerge = splitPointsMerge; this.fragmentExtractor = new FragmentExtractor(jprogram, jsprogram, map); this.initialLoadSequence = new LinkedHashSet<Integer>(jprogram.getSplitPointInitialSequence()); @@ -583,6 +656,11 @@ splitPointToFragmentMap[i] = i; } + this.splitPointToCodeIndexMap = new int[jprogram.getRunAsyncs().size() + 1]; + for (int i = 0; i < splitPointToCodeIndexMap.length; i++) { + splitPointToCodeIndexMap[i] = 0; + } + // TODO(acleung): I don't full understand this. This is mostly from the old // algorithm which patches up certain dependency after the control flow analysis. fieldToLiteralOfClass = buildFieldToClassLiteralMap(jprogram); @@ -612,6 +690,31 @@ fragmentStats.put(splitPoint, stats); } + private ControlFlowAnalyzer computeAllButNCfas( + ControlFlowAnalyzer liveAfterInitialSequence, List<Integer> sp) { + List<ControlFlowAnalyzer> allButOnes = new ArrayList<ControlFlowAnalyzer>(); + ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(liveAfterInitialSequence); + for (JRunAsync otherRunAsync : jprogram.getRunAsyncs()) { + if (isInitial(otherRunAsync.getSplitPoint())) { + continue; + } + if (sp.contains(otherRunAsync.getSplitPoint())) { + continue; + } + cfa.traverseFromRunAsync(otherRunAsync); + } + return cfa; + } + + /** + * Compute a CFA that covers the entire live code of the program. + */ + private ControlFlowAnalyzer computeCompleteCfa() { + ControlFlowAnalyzer everything = new ControlFlowAnalyzer(jprogram); + everything.traverseEverything(); + return everything; + } + private ControlFlowAnalyzer computeLiveSet( ControlFlowAnalyzer initiallyLive, LiveSplitPointMap liveness, JRunAsync runAsync) { // Control Flow Analysis from a split point. @@ -628,7 +731,7 @@ // Step #1: Compute all the initially live atoms that are part of entry points // class inits..etc. - initiallyLive = computeInitiallyLive(jprogram, NULL_RECORDER); + initiallyLive = computeInitiallyLive(jprogram, CodeSplitter.NULL_RECORDER); recordLiveSet(initiallyLive, liveness, 0); // Step #2: Incrementally add each split point that are classified as initial load sequence. @@ -656,7 +759,10 @@ partitionFragments(); // Step #6: Extract fragments using the partition algorithm. - extractStatements(initiallyLive); + extractStatements(computeInitiallyLive(jprogram, CodeSplitter.NULL_RECORDER)); + + // Step #7: Replaces the splitpoint number with the new fragment number. + replaceFragmentId(); } private void extractStatements(ControlFlowAnalyzer initiallyLive) { @@ -670,29 +776,44 @@ addFragment(0, alreadyLoaded, liveNow, noStats, fragmentStats); } - final List<Predicate<BitSet>> exclusivePredicates = new LinkedList<Predicate<BitSet>>(); - - // Signifies what has been already loaded in the initial fragment. - LivenessPredicate alreadyLoaded = new LiveSplitPointsPredicate(liveness, new Predicate<BitSet>() { - @Override - public boolean apply(BitSet value) { - // Live if it is used by the first fragment. - if (value.get(0)) { - return true; - } else { - for (int sp : initialLoadSequence) { - if (value.get(sp)) { - return true; - } - } - } - return false; + ControlFlowAnalyzer liveAfterInitialSequence = new ControlFlowAnalyzer(initiallyLive); + + int cacheIndex = 1; + // Initial Split Point. + { + for (final int sp : initialLoadSequence) { + splitPointToCodeIndexMap[sp] = cacheIndex; + LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(liveAfterInitialSequence); + ControlFlowAnalyzer liveAfterSp = new ControlFlowAnalyzer(liveAfterInitialSequence); + JRunAsync runAsync = jprogram.getRunAsyncs().get(sp - 1); + liveAfterSp.traverseFromRunAsync(runAsync); + LivenessPredicate liveNow = new CfaLivenessPredicate(liveAfterSp); + List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex); + addFragment(sp, alreadyLoaded, liveNow, statsToAppend, fragmentStats); + liveAfterInitialSequence = liveAfterSp; + cacheIndex++; } - }); + } + ControlFlowAnalyzer everything = computeCompleteCfa(); + Set<JField> allFields = new HashSet<JField>(); + Set<JMethod> allMethods = new HashSet<JMethod>(); + + for (JNode node : everything.getLiveFieldsAndMethods()) { + if (node instanceof JField) { + allFields.add((JField) node); + } + if (node instanceof JMethod) { + allMethods.add((JMethod) node); + } + } + allFields.addAll(everything.getFieldsWritten()); + // Search for all the atoms that are exclusively needed in each split point. for (int i = 1; i < splitPointToFragmentMap.length; i++) { + ArrayList<Integer> splitPoints = new ArrayList<Integer>(); + // This mean split point [i] has been merged with another split point, ignore it. if (splitPointToFragmentMap[i] != i) { continue; @@ -703,64 +824,54 @@ continue; } - // Creates a mask that is used to check if an atom is exclusively needed by the current - // fragment as well as all the fragment is was merged with. - final BitSet mask = new BitSet(splitPointToFragmentMap.length); - mask.set(i); + splitPoints.add(i); + splitPointToCodeIndexMap[i] = cacheIndex; + + for (int j = i + 1; j < splitPointToFragmentMap.length; j++) { if (initialLoadSequence.contains(j)) { continue; } if (splitPointToFragmentMap[j] == i) { - mask.set(j); + splitPointToCodeIndexMap[j] = cacheIndex; + splitPoints.add(j); } } - - // Creates a predicate - final Predicate<BitSet> pred = new Predicate<BitSet>() { - @Override - public boolean apply(BitSet value) { - if (value.get(0)) { - return true; - } - for (int sp : initialLoadSequence) { - if (value.get(sp)) { - return true; - } - } - - BitSet valueOrMask = (BitSet) value.clone(); - valueOrMask.or(mask); - - // Returns true if it at least matches one of the set field in the mask but not - // having a field set that is unset in the mask. - return value.intersects(mask) && valueOrMask.cardinality() <= mask.cardinality(); - } - }; - exclusivePredicates.add(pred); + ControlFlowAnalyzer allButOne = computeAllButNCfas(liveAfterInitialSequence, splitPoints); + Set<JNode> allLiveNodes = + union(allButOne.getLiveFieldsAndMethods(), allButOne.getFieldsWritten()); + updateReverseMap(i, fragmentMap.fields, allLiveNodes, allFields); + updateReverseMap(i, fragmentMap.methods, allButOne.getLiveFieldsAndMethods(), allMethods); + updateReverseMap(i, fragmentMap.strings, allButOne.getLiveStrings(), everything + .getLiveStrings()); + updateReverseMap(i, fragmentMap.types, declaredTypesIn(allButOne.getInstantiatedTypes()), + declaredTypesIn(everything.getInstantiatedTypes())); + + // This mean split point [i] has been merged with another split point, ignore it. + if (splitPointToFragmentMap[i] != i) { + continue; + } - LivenessPredicate liveNow = new LiveSplitPointsPredicate(liveness, pred); - List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(i); + // This was needed in the initial load sequence, ignore it. + if (initialLoadSequence.contains(i)) { + continue; + } + + LivenessPredicate alreadyLoaded = new ExclusivityMapLivenessPredicate(fragmentMap, 0); + LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, i); + List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex); addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats); + cacheIndex++; } - - // Left over fragments. - { - LivenessPredicate liveNow = new LiveSplitPointsPredicate(liveness, new Predicate<BitSet>() { - @Override - public boolean apply(BitSet value) { - for (Predicate<BitSet> p : exclusivePredicates) { - if (p.apply(value)) { - return false; - } - } - return true; - } - }); - - List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(splitPointToFragmentMap.length); + /* + * Compute the leftovers fragment. + */ + { + LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(liveAfterInitialSequence); + LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(fragmentMap, 0); + List<JsStatement> statsToAppend = fragmentExtractor.createOnLoadedCall(cacheIndex); addFragment(splitPointToFragmentMap.length, alreadyLoaded, liveNow, statsToAppend, fragmentStats); } @@ -773,16 +884,17 @@ fragBlock.getStatements().addAll(fragmentStats.get(i)); } - // TODO(acleung): This is an hack-ish way to put share the fragment map. - // jprogram.splitPointToFragmentMap = splitPointToFragmentMap; + jprogram.setFragmentPartitioningResult( + new FragmentPartitioningResult(splitPointToCodeIndexMap, fragmentStats.size())); } - + private void fixUpLoadOrderDependencies(LiveSplitPointMap fragmentMap, int splitPoint) { fixUpLoadOrderDependenciesForMethods(fragmentMap, splitPoint); fixUpLoadOrderDependenciesForTypes(fragmentMap, splitPoint); fixUpLoadOrderDependenciesForClassLiterals(fragmentMap, splitPoint); fixUpLoadOrderDependenciesForFieldsInitializedToStrings(fragmentMap, splitPoint); } + private void fixUpLoadOrderDependenciesForClassLiterals(LiveSplitPointMap fragmentMap, int splitPoint) { int numClassLitStrings = 0; int numFixups = 0; @@ -825,7 +937,6 @@ } } } - private void fixUpLoadOrderDependenciesForMethods(LiveSplitPointMap fragmentMap, int splitPoint) { int numFixups = 0; @@ -870,6 +981,10 @@ } } } + + private boolean isInitial(int entry) { + return initialLoadSequence.contains(entry); + } /** * We haves pinned down that fragment partition is an NP-Complete problem that maps right to @@ -895,7 +1010,7 @@ countShardedAtomsOfType(liveness.strings, matrix); countShardedAtomsOfType(liveness.types, matrix); - for (int c = 0; c < NUM_SPLITPOINTS_TO_MERGE; c++) { + for (int c = 0; c < splitPointsMerge; c++) { int bestI = 0, bestJ = 0, max = 0; for (int i = 1; i < splitPointToFragmentMap.length; i++) { if (initialLoadSequence.contains(i)) { @@ -931,7 +1046,21 @@ } } } - + + private void replaceFragmentId() { + (new JsModVisitor() { + @Override + public void endVisit(JsNumericEntry x, JsContext ctx) { + if (x.getKey().equals("RunAsyncFragmentIndex")) { + x.setValue(splitPointToCodeIndexMap[x.getValue()]); + } + if (x.getKey().equals("RunAsyncFragmentCount")) { + x.setValue(jsprogram.getFragmentCount() - 1); + } + } + }).accept(jsprogram); + } + /** * Traverse <code>exp</code> and find all string literals within it. */
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java index 7120ab7..8c42325 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -15,10 +15,10 @@ */ package com.google.gwt.dev.jjs.impl; -import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.linker.CastableTypeMap; import com.google.gwt.core.ext.linker.impl.StandardCastableTypeMap; import com.google.gwt.core.ext.linker.impl.StandardSymbolData; +import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.dev.jjs.HasSourceInfo; import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.JsOutputOption; @@ -68,6 +68,7 @@ import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JNewInstance; import com.google.gwt.dev.jjs.ast.JNode; +import com.google.gwt.dev.jjs.ast.JNumericEntry; import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JParameterRef; import com.google.gwt.dev.jjs.ast.JPostfixOperation; @@ -130,6 +131,7 @@ import com.google.gwt.dev.js.ast.JsNormalScope; import com.google.gwt.dev.js.ast.JsNullLiteral; import com.google.gwt.dev.js.ast.JsNumberLiteral; +import com.google.gwt.dev.js.ast.JsNumericEntry; import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsParameter; import com.google.gwt.dev.js.ast.JsPostfixOperation; @@ -1248,7 +1250,12 @@ popList(newOp.getArguments(), x.getArgs().size()); // args push(newOp); } - + + @Override + public void endVisit(JNumericEntry x, Context ctx) { + push(new JsNumericEntry(x.getSourceInfo(), x.getKey(), x.getValue())); + } + @Override public void endVisit(JParameter x, Context ctx) { push(new JsParameter(x.getSourceInfo(), names.get(x)));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java index 3543d4e..88cfd1c 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -264,7 +264,7 @@ // The target method was already pruned (TypeTightener will fix this). return; } - + // Let's do it! toBeMadeStatic.add(method); } @@ -288,7 +288,8 @@ public void endVisit(JMethodCall x, Context ctx) { JMethod oldMethod = x.getTarget(); JMethod newMethod = program.getStaticImpl(oldMethod); - if (newMethod == null || x.canBePolymorphic()) { + + if (newMethod == null || x.canBePolymorphic()) { return; } @@ -302,6 +303,7 @@ */ return; } + ctx.replaceMe(makeStaticCall(x, newMethod)); } @@ -313,7 +315,7 @@ @Override public boolean visit(JProgram x, Context ctx) { - initiallyLive = CodeSplitter.computeInitiallyLive(x); + initiallyLive = CodeSplitter2.computeInitiallyLive(x); return true; } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java index 7d50f49..2e9222a 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
@@ -24,10 +24,10 @@ import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; -import com.google.gwt.dev.jjs.ast.JIntLiteral; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; +import com.google.gwt.dev.jjs.ast.JNumericEntry; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; @@ -100,7 +100,7 @@ JMethod runAsyncMethod = program.getIndexedMethod("AsyncFragmentLoader.runAsync"); assert runAsyncMethod != null; JMethodCall runAsyncCall = new JMethodCall(info, null, runAsyncMethod); - runAsyncCall.addArg(JIntLiteral.get(splitPoint)); + runAsyncCall.addArg(new JNumericEntry(info, "RunAsyncFragmentIndex", splitPoint)); runAsyncCall.addArg(asyncCallback); JReferenceType callbackType = (JReferenceType) asyncCallback.getType(); @@ -188,7 +188,7 @@ JMethodCall newCall = new JMethodCall(info, null, program .getIndexedMethod("RunAsyncCode.forSplitPointNumber")); - newCall.addArg(program.getLiteralInt(splitPoint)); + newCall.addArg(new JNumericEntry(info, "RunAsyncFragmentIndex", splitPoint)); ctx.replaceMe(newCall); } } @@ -269,6 +269,7 @@ private void setNumEntriesInAsyncFragmentLoader(int entryCount) { JMethodCall constructorCall = getBrowserLoaderConstructor(program); assert constructorCall.getArgs().get(0).getType() == JPrimitiveType.INT; - constructorCall.setArg(0, program.getLiteralInt(entryCount)); + constructorCall.setArg(0, + new JNumericEntry(constructorCall.getSourceInfo(), "RunAsyncFragmentCount", entryCount)); } }
diff --git a/dev/core/src/com/google/gwt/dev/js/JsHoister.java b/dev/core/src/com/google/gwt/dev/js/JsHoister.java index 2917644..22ebc87 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsHoister.java +++ b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
@@ -30,6 +30,7 @@ import com.google.gwt.dev.js.ast.JsNew; import com.google.gwt.dev.js.ast.JsNullLiteral; import com.google.gwt.dev.js.ast.JsNumberLiteral; +import com.google.gwt.dev.js.ast.JsNumericEntry; import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsPostfixOperation; import com.google.gwt.dev.js.ast.JsPrefixOperation; @@ -172,6 +173,11 @@ public void endVisit(JsNumberLiteral x, JsContext ctx) { stack.push(x); } + + @Override + public void endVisit(JsNumericEntry x, JsContext ctx) { + stack.push(x); + } @Override public void endVisit(JsObjectLiteral x, JsContext ctx) {
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 564191c..a10de34 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -48,6 +48,7 @@ import com.google.gwt.dev.js.ast.JsNew; import com.google.gwt.dev.js.ast.JsNullLiteral; import com.google.gwt.dev.js.ast.JsNumberLiteral; +import com.google.gwt.dev.js.ast.JsNumericEntry; import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsOperator; import com.google.gwt.dev.js.ast.JsParameter; @@ -781,6 +782,12 @@ } return false; } + + @Override + public boolean visit(JsNumericEntry x, JsContext ctx) { + p.print(Integer.toString(x.getValue())); + return false; + } @Override public boolean visit(JsObjectLiteral x, JsContext ctx) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNumericEntry.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNumericEntry.java new file mode 100644 index 0000000..68e0025 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNumericEntry.java
@@ -0,0 +1,70 @@ +/* + * Copyright 2011 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.js.ast; + +import com.google.gwt.dev.jjs.SourceInfo; + +/** + * Represent an index that can be replacable by the compiler at compile time. + */ +public final class JsNumericEntry extends JsExpression { + private final String key; + private int value; + + public JsNumericEntry(SourceInfo info, String key, int value) { + super(info); + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + @Override + public NodeKind getKind() { + return NodeKind.NUMBER; + } + + public int getValue() { + return value; + } + + @Override + public boolean hasSideEffects() { + return false; + } + + @Override + public boolean isDefinitelyNotNull() { + return true; + } + + @Override + public boolean isDefinitelyNull() { + return false; + } + + public void setValue(int value) { + this.value = value; + } + + @Override + public void traverse(JsVisitor v, JsContext ctx) { + v.visit(this, ctx); + v.endVisit(this, ctx); + } +}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java b/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java index ba5396c..77e27fb 100644 --- a/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java
@@ -202,6 +202,9 @@ public void endVisit(JsNumberLiteral x, JsContext ctx) { } + + public void endVisit(JsNumericEntry x, JsContext ctx) { + } public void endVisit(JsObjectLiteral x, JsContext ctx) { } @@ -361,6 +364,10 @@ public boolean visit(JsNumberLiteral x, JsContext ctx) { return true; } + + public boolean visit(JsNumericEntry x, JsContext ctx) { + return true; + } public boolean visit(JsObjectLiteral x, JsContext ctx) { return true;
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerFragmentMerge.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerFragmentMerge.java new file mode 100644 index 0000000..fde83e1 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerFragmentMerge.java
@@ -0,0 +1,50 @@ +/* + * Copyright 2011 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.util.arg; + +import com.google.gwt.util.tools.ArgHandlerInt; + +/** + * An ArgHandler to provide the -XfragmentMerge flag. + */ +public class ArgHandlerFragmentMerge extends ArgHandlerInt { + + private final OptionFragmentsMerge option; + + public ArgHandlerFragmentMerge(OptionFragmentsMerge option) { + this.option = option; + } + + @Override + public String getPurpose() { + return "EXPERIMENTAL: Enables Fragment merging code splitter."; + } + + @Override + public String getTag() { + return "-XfragmentMerge"; + } + + @Override + public String[] getTagArgs() { + return new String[] {"numFragments"}; + } + + @Override + public void setInt(int value) { + option.setFragmentsMerge(value); + } +}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionFragmentsMerge.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionFragmentsMerge.java new file mode 100644 index 0000000..42d4ab1 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionFragmentsMerge.java
@@ -0,0 +1,35 @@ +/* + * Copyright 2011 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.util.arg; + +/** + * Enable the new code splitter that auto-partitions. + */ +public interface OptionFragmentsMerge { + + // TODO(acleung): This is currently an experimental frag. We should find a + // use case new splitter. Some possible approache: + // + // 1. Magically decide the number of fragments to merge. (May be too hard) + // 2. All the user to specify number of fragments they want to *keep* instead + // of the number they want to merge. + // 3. Ask the user what is the max (average) size of fragments. (This + // can only be an estimated. + + int getFragmentsMerge(); + + void setFragmentsMerge(int numFragments); +}
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 9fa40e1..b14b839 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
@@ -217,7 +217,7 @@ JavaToJavaScriptMap map = GenerateJavaScriptAST.exec( jProgram, jsProgram, JsOutputOption.PRETTY, symbolTable, new PropertyOracle[]{ new StaticPropertyOracle(orderedProps, orderedPropValues, configProps)}); - CodeSplitter2.exec(logger, jProgram, jsProgram, map, null); + CodeSplitter2.exec(logger, jProgram, jsProgram, map, 4, null); } private static String createRunAsync(String body) {