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);