Change the way code-splitting and string interning is performed on the JS AST to allow new strings to be introduced by JsVisitors (such as JsStackEmulator). Patch by: bobv, spoon (JProgram) Review by: spoon, bobv git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5812 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 d99c393..874fc6f 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -42,14 +42,12 @@ import com.google.gwt.dev.jjs.ast.JClassType; 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.JGwtCreate; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodBody; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReboundEntryPoint; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JStatement; import com.google.gwt.dev.jjs.impl.ArrayNormalizer; import com.google.gwt.dev.jjs.impl.AssertionNormalizer; @@ -99,7 +97,6 @@ import com.google.gwt.dev.js.JsVerboseNamer; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsProgram; -import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.util.AbstractTextOutput; import com.google.gwt.dev.util.DefaultTextOutput; import com.google.gwt.dev.util.Empty; @@ -120,7 +117,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -140,7 +136,7 @@ private final byte[] serializedSymbolMap; private final StatementRanges[] statementRanges; private final int permutationId; - + public PermutationResultImpl(String[] js, SymbolData[] symbolMap, StatementRanges[] statementRanges, int permutationId) { byte[][] bytes = new byte[js.length][]; @@ -167,7 +163,7 @@ public byte[][] getJs() { return js; } - + public int getPermutationId() { return permutationId; } @@ -270,34 +266,11 @@ } while (didChange); } - // (10) Obfuscate - final Map<JsName, String> stringLiteralMap; - switch (options.getOutput()) { - case OBFUSCATED: - stringLiteralMap = JsStringInterner.exec(jsProgram); - JsObfuscateNamer.exec(jsProgram); - break; - case PRETTY: - // We don't intern strings in pretty mode to improve readability - stringLiteralMap = new HashMap<JsName, String>(); - JsPrettyNamer.exec(jsProgram); - break; - case DETAILED: - stringLiteralMap = JsStringInterner.exec(jsProgram); - JsVerboseNamer.exec(jsProgram); - break; - default: - throw new InternalCompilerException("Unknown output mode"); - } - - JavaToJavaScriptMap postStringInterningMap = addStringLiteralMap(map, - stringLiteralMap); - - // (10.5) Split up the program into fragments + // (10) Split up the program into fragments SoycArtifact dependencies = null; if (options.isAggressivelyOptimize() && options.isRunAsyncEnabled()) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CodeSplitter.exec(logger, jprogram, jsProgram, postStringInterningMap, + CodeSplitter.exec(logger, jprogram, jsProgram, map, chooseDependencyRecorder(options.isSoycEnabled(), baos)); if (baos.size() > 0) { dependencies = new SoycArtifact("dependencies" + permutationId @@ -305,6 +278,24 @@ } } + // (10.5) Obfuscate + switch (options.getOutput()) { + case OBFUSCATED: + JsStringInterner.exec(jprogram, jsProgram); + JsObfuscateNamer.exec(jsProgram); + break; + case PRETTY: + // We don't intern strings in pretty mode to improve readability + JsPrettyNamer.exec(jsProgram); + break; + case DETAILED: + JsStringInterner.exec(jprogram, jsProgram); + JsVerboseNamer.exec(jsProgram); + break; + default: + throw new InternalCompilerException("Unknown output mode"); + } + // (11) Perform any post-obfuscation normalizations. // Work around an IE7 bug, @@ -340,7 +331,8 @@ makeSymbolMap(symbolTable), ranges, permutationId); toReturn.getArtifacts().add( - makeSoycArtifact(logger, permutationId, jprogram, js, sourceInfoMaps, dependencies)); + makeSoycArtifact(logger, permutationId, jprogram, js, sourceInfoMaps, + dependencies)); System.out.println("Permutation took " + (System.currentTimeMillis() - permStart) + " ms"); @@ -622,40 +614,6 @@ return didChange; } - private static JavaToJavaScriptMap addStringLiteralMap( - final JavaToJavaScriptMap map, final Map<JsName, String> stringLiteralMap) { - JavaToJavaScriptMap postStringInterningMap = new JavaToJavaScriptMap() { - public JsName nameForMethod(JMethod method) { - return map.nameForMethod(method); - } - - public JsName nameForType(JReferenceType type) { - return map.nameForType(type); - } - - public JField nameToField(JsName name) { - return map.nameToField(name); - } - - public JMethod nameToMethod(JsName name) { - return map.nameToMethod(name); - } - - public String stringLiteralForName(JsName name) { - return stringLiteralMap.get(name); - } - - public JReferenceType typeForStatement(JsStatement stat) { - return map.typeForStatement(stat); - } - - public JMethod vtableInitToMethod(JsStatement stat) { - return map.vtableInitToMethod(stat); - } - }; - return postStringInterningMap; - } - private static void checkForErrors(TreeLogger logger, CompilationUnitDeclaration[] cuds, boolean itemizeErrors) throws UnableToCompleteException {
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 38e7411..6ba32ab 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
@@ -24,6 +24,7 @@ import com.google.gwt.dev.jjs.ast.js.JClassSeed; import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; import com.google.gwt.dev.jjs.ast.js.JsonObject; +import com.google.gwt.dev.jjs.impl.CodeSplitter; import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs.RunAsyncReplacement; import com.google.gwt.dev.util.collect.Lists; import com.google.gwt.dev.util.collect.Maps; @@ -149,6 +150,26 @@ return traceMethods.size() > 0; } + /** + * The same as {@link #lastFragmentLoadingBefore(int, int...)}, except that + * all of the parameters must be passed explicitly. The instance method should + * be preferred whenever a JProgram instance is available. + * + * @param initialSeq The initial split point sequence of the program + * @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) { + int latest = firstFragment; + for (int frag : restFragments) { + latest = pairwiseLastFragmentLoadingBefore(initialSeq, numSps, latest, + frag); + } + return latest; + } + private static String dotify(char[][] name) { StringBuffer result = new StringBuffer(); for (int i = 0; i < name.length; ++i) { @@ -161,6 +182,51 @@ return result.toString(); } + /** + * 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) { + if (frag1 == frag2) { + return frag1; + } + + if (frag1 == 0) { + return 0; + } + + if (frag2 == 0) { + return 0; + } + + // See if either is in the initial sequence + int initPos1 = initialSeq.indexOf(frag1); + int initPos2 = initialSeq.indexOf(frag2); + + // If both are in the initial sequence, then pick the earlier + if (initPos1 >= 0 && initPos2 >= 0) { + if (initPos1 < initPos2) { + return frag1; + } + return frag2; + } + + // If exactly one is in the initial sequence, then it's the earlier one + if (initPos1 >= 0) { + return frag1; + } + if (initPos2 >= 0) { + return frag2; + } + + assert (initPos1 < 0 && initPos2 < 0); + assert (frag1 != frag2); + + // They are both leftovers or exclusive. Leftovers goes first in all cases. + return CodeSplitter.getLeftoversFragmentNumber(numSps); + } + public final List<JClassType> codeGenTypes = new ArrayList<JClassType>(); /** @@ -1026,6 +1092,16 @@ return staticToInstanceMap.containsKey(method); } + /** + * Given a sequence of fragment numbers, return the latest fragment number + * possible that does not load later than any of these. It might be one of the + * supplied fragments, or it might be a common predecessor. + */ + public int lastFragmentLoadingBefore(int firstFragment, int... restFragments) { + return lastFragmentLoadingBefore(splitPointInitialSequence, + entryMethods.size() - 1, firstFragment, restFragments); + } + public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) { // Make it into a source type name. String srcTypeName = qualifiedBinaryName.replace('$', '.');
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 22bd0ce..214008f 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
@@ -150,10 +150,6 @@ if (field != null) { System.out.println(fullNameString(field)); } - String string = map.stringLiteralForName(var.getName()); - if (string != null) { - System.out.println("STRING " + var.getName()); - } } } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java index 0bfedd0..864be36 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
@@ -98,8 +98,8 @@ * * <li>Instance methods depend on their enclosing type.</li> * - * <li>Static fields that are initialized to strings depend on the string - * they are initialized to.</li> + * <li>Static fields that are initialized to strings depend on the string they + * are initialized to.</li> * </ul> */ public static interface LivenessPredicate { @@ -115,8 +115,8 @@ * Whether miscellelaneous statements should be considered live. * Miscellaneous statements are any that the fragment extractor does not * recognize as being in any particular category. This method should almost - * always return <code>true</code>, but does return <code>false</code> - * for {@link NothingAlivePredicate}. + * always return <code>true</code>, but does return <code>false</code> for + * {@link NothingAlivePredicate}. */ boolean miscellaneousStatementsAreLive(); } @@ -149,7 +149,8 @@ /** * A logger for statements that the fragment extractor encounters. Install one * using - * {@link FragmentExtractor#setStatementLogger(com.google.gwt.fragserv.FragmentExtractor.StatementLogger)}. + * {@link FragmentExtractor#setStatementLogger(com.google.gwt.fragserv.FragmentExtractor.StatementLogger)} + * . */ public static interface StatementLogger { void logStatement(JsStatement stat, boolean isIncluded); @@ -204,7 +205,8 @@ /** * Create a call to - * {@link com.google.gwt.core.client.impl.AsyncFragmentLoader#leftoversFragmentHasLoaded()}. + * {@link com.google.gwt.core.client.impl.AsyncFragmentLoader#leftoversFragmentHasLoaded()} + * . */ public List<JsStatement> createCallToLeftoversFragmentHasLoaded() { JMethod loadedMethod = jprogram.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded"); @@ -315,16 +317,12 @@ * individual vars that could be removed. If it does, then * {@link #removeSomeVars(JsVars, LivenessPredicate, LivenessPredicate)} is * sensible for this statement and should be used instead of - * {@link #isLive(JsStatement, com.google.gwt.fragserv.FragmentExtractor.LivenessPredicate)}. + * {@link #isLive(JsStatement, com.google.gwt.fragserv.FragmentExtractor.LivenessPredicate)} + * . */ private boolean containsRemovableVars(JsStatement stat) { if (stat instanceof JsVars) { for (JsVar var : (JsVars) stat) { - String lit = map.stringLiteralForName(var.getName()); - if (lit != null) { - // It's an intern variable for a string literal - return true; - } JField field = map.nameToField(var.getName()); if (field != null) { @@ -396,12 +394,6 @@ * {@link #containsRemovableVars(JsStatement)}. */ private boolean isLive(JsVar var, LivenessPredicate livenessPredicate) { - String lit = map.stringLiteralForName(var.getName()); - if (lit != null) { - // It's an intern variable for a string literal - return livenessPredicate.isLive(lit); - } - JField field = map.nameToField(var.getName()); if (field != null) { // It's a field @@ -414,9 +406,9 @@ /** * Return the Java method corresponding to <code>stat</code>, or - * <code>null</code> if there isn't one. It recognizes JavaScript of the - * form <code>function foo(...) { ...}</code>, where <code>foo</code> is - * the name of the JavaScript translation of a Java method. + * <code>null</code> if there isn't one. It recognizes JavaScript of the form + * <code>function foo(...) { ...}</code>, where <code>foo</code> is the name + * of the JavaScript translation of a Java method. */ private JMethod methodFor(JsStatement stat) { if (stat instanceof JsExprStmt) { @@ -472,8 +464,8 @@ /** * Compute a statement that can be used to set up for installing instance * methods into a vtable. It will be of the form - * <code>_ = foo.prototype</code>, where <code>foo</code> is the - * constructor function for <code>vtableType</code>. + * <code>_ = foo.prototype</code>, where <code>foo</code> is the constructor + * function for <code>vtableType</code>. */ private JsStatement vtableStatFor(JReferenceType vtableType) { JsNameRef prototypeField = new JsNameRef(
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 b8f03dc..d56c411 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
@@ -2133,14 +2133,6 @@ return nameToMethodMap.get(name); } - public String stringLiteralForName(JsName var) { - /* - * This method will be supplied non-trivially elsewhere. - * GenerateJavaScriptAST doesn't have the information to implement it. - */ - return null; - } - public JReferenceType typeForStatement(JsStatement stat) { return typeForStatMap.get(stat); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java index 996fce0..7a96d82 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java
@@ -49,12 +49,6 @@ JMethod nameToMethod(JsName name); /** - * If <code>var</code> is the name of the variable used to hold an interned - * string literal, then return the string it interns. Otherwise, return null. - */ - String stringLiteralForName(JsName var); - - /** * If <code>stat</code> is used to set up the definition of some class, return * that class. Otherwise, return null. */
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java b/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java index 4c10370..415737a 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java +++ b/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java
@@ -16,6 +16,7 @@ package com.google.gwt.dev.js; import com.google.gwt.dev.jjs.SourceInfo; +import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.js.ast.JsBinaryOperation; import com.google.gwt.dev.js.ast.JsBlock; import com.google.gwt.dev.js.ast.JsContext; @@ -25,33 +26,48 @@ import com.google.gwt.dev.js.ast.JsPostfixOperation; import com.google.gwt.dev.js.ast.JsPrefixOperation; import com.google.gwt.dev.js.ast.JsProgram; +import com.google.gwt.dev.js.ast.JsProgramFragment; import com.google.gwt.dev.js.ast.JsPropertyInitializer; import com.google.gwt.dev.js.ast.JsScope; import com.google.gwt.dev.js.ast.JsStringLiteral; import com.google.gwt.dev.js.ast.JsVars; import com.google.gwt.dev.js.ast.JsVars.JsVar; +import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; +import java.util.SortedSet; import java.util.TreeMap; +import java.util.TreeSet; /** * Interns all String literals in a JsProgram. Each unique String will be - * assigned to a variable in the program's main block and instances of a - * JsStringLiteral replaced with a JsNameRef. This optimization is complete in a - * single pass, although it may be performed multiple times without duplicating - * the intern pool. + * assigned to a variable in an appropriate program fragment and the + * JsStringLiteral will be replaced with a JsNameRef. This optimization is + * complete in a single pass, although it may be performed multiple times + * without duplicating the intern pool. */ public class JsStringInterner { - /** * Replaces JsStringLiterals with JsNameRefs, creating new JsName allocations * on the fly. */ private static class StringVisitor extends JsModVisitor { /** + * The current fragment being visited. + */ + private int currentFragment = 0; + + /** + * This map records which program fragment the variable for this JsName + * should be created in. + */ + private final SortedMap<JsStringLiteral, Integer> fragmentAssignment = new TreeMap<JsStringLiteral, Integer>( + LITERAL_COMPARATOR); + + /** * A counter used for assigning ids to Strings. Even though it's unlikely * that someone would actually have two billion strings in their * application, it doesn't hurt to think ahead. @@ -59,7 +75,13 @@ private long lastId = 0; /** - * Records the scope in which the interned identifiers should be unique. + * Only used to get fragment load order so strings used in multiple + * fragments need only be downloaded once. + */ + private final JProgram program; + + /** + * Records the scope in which the interned identifiers are declared. */ private final JsScope scope; @@ -68,11 +90,7 @@ * lexicographical ordering of the string constant. */ private final SortedMap<JsStringLiteral, JsName> toCreate = new TreeMap<JsStringLiteral, JsName>( - new Comparator<JsStringLiteral>() { - public int compare(JsStringLiteral o1, JsStringLiteral o2) { - return o1.getValue().compareTo(o2.getValue()); - } - }); + LITERAL_COMPARATOR); /** * Constructor. @@ -80,10 +98,16 @@ * @param scope specifies the scope in which the interned strings should be * created. */ - public StringVisitor(JsScope scope) { + public StringVisitor(JProgram program, JsScope scope) { + this.program = program; this.scope = scope; } + @Override + public void endVisit(JsProgramFragment x, JsContext<JsProgramFragment> ctx) { + currentFragment++; + } + /** * Prevents 'fixing' an otherwise illegal operation. */ @@ -133,6 +157,24 @@ toCreate.put(x, name); } + Integer currentAssignment = fragmentAssignment.get(x); + if (currentAssignment == null) { + // Assign the JsName to the current program fragment + fragmentAssignment.put(x, currentFragment); + + } else if (currentAssignment != currentFragment) { + // See if we need to move the assignment to a common ancestor + assert program != null : "JsStringInterner cannot be used with " + + "fragmented JsProgram without an accompanying JProgram"; + + int newAssignment = program.lastFragmentLoadingBefore(currentFragment, + currentAssignment); + if (newAssignment != currentAssignment) { + // Assign the JsName to the common ancestor + fragmentAssignment.put(x, newAssignment); + } + } + ctx.replaceMe(name.makeRef(x.getSourceInfo().makeChild( JsStringInterner.class, "Interned reference"))); @@ -149,9 +191,45 @@ } } + private static final Comparator<JsStringLiteral> LITERAL_COMPARATOR = new Comparator<JsStringLiteral>() { + public int compare(JsStringLiteral o1, JsStringLiteral o2) { + return o1.getValue().compareTo(o2.getValue()); + } + }; + public static final String PREFIX = "$intern_"; /** + * Apply interning of String literals to a JsProgram. The symbol names for the + * interned strings will be defined within the program's top scope and the + * symbol declarations will be added as the first statement in the program's + * global block. + * + * @param jprogram the JProgram that has fragment dependency data for + * <code>program</code> + * @param program the JsProgram + */ + public static void exec(JProgram jprogram, JsProgram program) { + StringVisitor v = new StringVisitor(jprogram, program.getScope()); + v.accept(program); + + Map<Integer, SortedSet<JsStringLiteral>> bins = new HashMap<Integer, SortedSet<JsStringLiteral>>(); + for (int i = 0, j = program.getFragmentCount(); i < j; i++) { + bins.put(i, new TreeSet<JsStringLiteral>(LITERAL_COMPARATOR)); + } + for (Map.Entry<JsStringLiteral, Integer> entry : v.fragmentAssignment.entrySet()) { + SortedSet<JsStringLiteral> set = bins.get(entry.getValue()); + assert set != null; + set.add(entry.getKey()); + } + + for (Map.Entry<Integer, SortedSet<JsStringLiteral>> entry : bins.entrySet()) { + createVars(program, program.getFragmentBlock(entry.getKey()), + entry.getValue(), v.toCreate); + } + } + + /** * Intern String literals that occur within a JsBlock. The symbol declarations * will be added as the first statement in the block. * @@ -160,50 +238,29 @@ * @return <code>true</code> if any changes were made to the block */ public static boolean exec(JsProgram program, JsBlock block, JsScope scope) { - StringVisitor v = new StringVisitor(scope); + StringVisitor v = new StringVisitor(null, scope); v.accept(block); - createVars(program, block, v.toCreate); + createVars(program, block, v.toCreate.keySet(), v.toCreate); return v.didChange(); } /** - * Apply interning of String literals to a JsProgram. The symbol names for the - * interned strings will be defined within the program's top scope and the - * symbol declarations will be added as the first statement in the program's - * global block. - * - * @param program the JsProgram - * @return a map from each created JsName to the string it is a literal for. + * Create variable declarations in <code>block</code> for literal strings + * <code>toCreate</code> using the variable map <code>names</code>. */ - public static Map<JsName, String> exec(JsProgram program) { - StringVisitor v = new StringVisitor(program.getScope()); - for (int i = 0; i < program.getFragmentCount(); i++) { - v.accept(program.getFragmentBlock(i)); - } - - Map<JsName, String> map = new HashMap<JsName, String>(); - for (JsStringLiteral stringLit : v.toCreate.keySet()) { - map.put(v.toCreate.get(stringLit), stringLit.getValue()); - } - - createVars(program, program.getGlobalBlock(), v.toCreate); - - return map; - } - private static void createVars(JsProgram program, JsBlock block, - SortedMap<JsStringLiteral, JsName> toCreate) { + Collection<JsStringLiteral> toCreate, Map<JsStringLiteral, JsName> names) { if (toCreate.size() > 0) { // Create the pool of variable names. JsVars vars = new JsVars(program.createSourceInfoSynthetic( JsStringInterner.class, "Interned string pool")); SourceInfo sourceInfo = program.createSourceInfoSynthetic( JsStringInterner.class, "Interned string assignment"); - for (Map.Entry<JsStringLiteral, JsName> entry : toCreate.entrySet()) { - JsVar var = new JsVar(sourceInfo, entry.getValue()); - var.setInitExpr(entry.getKey()); + for (JsStringLiteral literal : toCreate) { + JsVar var = new JsVar(sourceInfo, names.get(literal)); + var.setInitExpr(literal); vars.add(var); } block.getStatements().add(0, vars);
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java index dc073f9..bc50847 100644 --- a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java +++ b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
@@ -209,9 +209,7 @@ } /** - * Note: This is currently assumed not to be called after - * GenerateJavaScriptAST has finished. If it ever is, then GWT.runAsync needs - * to be updated to account for such string literals. + * Creates or retrieves a JsStringLiteral from an interned object pool. */ public JsStringLiteral getStringLiteral(SourceInfo sourceInfo, String value) { JsStringLiteral lit = stringLiteralMap.get(value);
diff --git a/dev/core/test/com/google/gwt/dev/javac/JProgramLastFragmentLoadingBeforeTest.java b/dev/core/test/com/google/gwt/dev/javac/JProgramLastFragmentLoadingBeforeTest.java new file mode 100644 index 0000000..585f38e --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/javac/JProgramLastFragmentLoadingBeforeTest.java
@@ -0,0 +1,129 @@ +/* + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.javac; + +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.util.collect.Lists; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * Tests {@link JProgram#lastFragmentLoadingBefore(int, int...)}. + */ +public class JProgramLastFragmentLoadingBeforeTest extends TestCase { + + public void testBasics() { + List<Integer> initialSeq = Lists.create(4, 3, 2); + int numSps = 10; + + // Very simple + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0, + 1, 2, 3)); + + // Equal fragments load at the same time + assertEquals(0, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0, 0)); + assertEquals(1, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 1, 1)); + assertEquals(3, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 3, 3)); + assertEquals(6, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 6, 6)); + assertEquals(11, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 11, + 11)); + + // Zero loads first + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 11, + 0)); + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 10, + 0)); + assertEquals(0, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 3, 0)); + + // Initial sequence fragments load before all others + assertEquals(3, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 3, + 10)); + assertEquals(3, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 10, + 3)); + + // Earlier initial sequence fragments load before the others + assertEquals(4, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 2, + 3, 4)); + assertEquals(3, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 2, 3)); + assertEquals(4, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 4, + 3, 2)); + + // For non-equal exclusive fragments, leftovers is the common predecessor + assertEquals(11, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 1, + 7)); + + // Leftovers is before any exclusive + assertEquals(11, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 7, + 11)); + } + + public void testWithEmptyInitial() { + List<Integer> initialSeq = Lists.create(); + int numSps = 10; + + // Simple case + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0, + 1, 2, 3)); + + // Equal fragments load at the same time + assertEquals(0, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0, 0)); + assertEquals(6, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 6, 6)); + assertEquals(11, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 11, + 11)); + + // Zero loads first + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 11, + 0)); + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 10, + 0)); + assertEquals(0, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 3, 0)); + + // For non-equal exclusive fragments, leftovers is the common predecessor + assertEquals(11, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 1, + 7)); + + // Leftovers is before any exclusive + assertEquals(11, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 7, + 11)); + + // With just one argument, return it + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0)); + assertEquals(3, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 3)); + assertEquals(11, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 11)); + } + + public void testWithNoSplitPoints() { + List<Integer> initialSeq = Lists.create(); + int numSps = 0; + + assertEquals(0, + JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0, 0)); + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0, + 0, 0)); + assertEquals(0, JProgram.lastFragmentLoadingBefore(initialSeq, numSps, 0)); + } +}
diff --git a/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java index c6efa75..9b0ceaa 100644 --- a/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java +++ b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
@@ -33,6 +33,7 @@ suite.addTestSuite(GWTProblemTest.class); suite.addTestSuite(JdtBehaviorTest.class); suite.addTestSuite(JdtCompilerTest.class); + suite.addTestSuite(JProgramLastFragmentLoadingBeforeTest.class); suite.addTestSuite(JSORestrictionsTest.class); suite.addTestSuite(JsniCheckerTest.class); suite.addTestSuite(TypeOracleMediatorTest.class);