Unified all interning logic into JsLiteralInterner.
Now any JsLiteral that is a value is interned by a JsLiteralInterner.
For now only strings, regexes, numbers and some object literals (e.g.
the ones for cast maps) are interned. Object literals that represent
long values should also be internable here, however they are interned
in GenerateJavaScriptLiterals to provide them with a nice variable
name.
A future compiler pass might mark other literals that have value
semantics (i.e. never changed, never compared by reference)s as
internable.
Change-Id: I163cac2622ee72c395bf8ae37ed67862ab74a082
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index f73bbec..c5a356d 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -34,12 +34,12 @@
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.JJSOptions;
import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.js.JsLiteralInterner;
import com.google.gwt.dev.js.JsObfuscateNamer;
import com.google.gwt.dev.js.JsParser;
import com.google.gwt.dev.js.JsParserException;
import com.google.gwt.dev.js.JsPrettyNamer;
import com.google.gwt.dev.js.JsSourceGenerationVisitor;
-import com.google.gwt.dev.js.JsStringInterner;
import com.google.gwt.dev.js.JsSymbolResolver;
import com.google.gwt.dev.js.JsUnusedFunctionRemover;
import com.google.gwt.dev.js.JsVerboseNamer;
@@ -74,7 +74,7 @@
public class StandardLinkerContext extends Linker implements LinkerContext {
/**
- * Applies the {@link JsStringInterner} optimization to each top-level
+ * Applies the {@link JsLiteralInterner} optimization to each top-level
* function defined within a JsProgram.
*/
private static class TopFunctionStringInterner extends JsModVisitor {
@@ -93,7 +93,7 @@
@Override
public boolean visit(JsFunction x, JsContext ctx) {
- didChange |= JsStringInterner.exec(program, x.getBody(), x.getScope(), true);
+ didChange |= JsLiteralInterner.exec(program, x.getBody(), x.getScope(), true);
return false;
}
}
@@ -446,8 +446,8 @@
switch (jjsOptions.getOutput()) {
case OBFUSCATED:
/*
- * We can't apply the regular JsStringInterner to the JsProgram that
- * we've just created. In the normal case, the JsStringInterner adds an
+ * We can't apply the regular JsLiteralInterner to the JsProgram that
+ * we've just created. In the normal case, the JsLiteralInterner adds an
* additional statement to the program's global JsBlock, however we
* don't know exactly what the form and structure of our JsProgram are,
* so we'll limit the scope of the modifications to each top-level
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java
index 880868e..4a1db2d 100644
--- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java
+++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -22,7 +22,9 @@
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
import com.google.gwt.dev.js.SizeBreakdown;
+import com.google.gwt.dev.js.ast.JsLiteral;
import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsStringLiteral;
import com.google.gwt.dev.util.Util;
import java.io.IOException;
@@ -98,7 +100,7 @@
* equivalents. The portion of the input string between start (inclusive) and
* end (exclusive) is scanned. The output is appended to the given
* StringBuilder.
- *
+ *
* @param code the input String
* @param start the first character position to scan.
* @param end the character position following the last character to scan.
@@ -173,7 +175,7 @@
* @param logger a TreeLogger
*/
public static void recordMap(TreeLogger logger, OutputStream out, SizeBreakdown[] sizeBreakdowns,
- JavaToJavaScriptMap jjsmap, Map<JsName, String> obfuscateMap) throws IOException {
+ JavaToJavaScriptMap jjsmap, Map<JsName, JsLiteral> internedLiteralByVariableName) throws IOException {
out = new GZIPOutputStream(out);
Writer writer = new OutputStreamWriter(out, Util.DEFAULT_ENCODING);
@@ -188,7 +190,8 @@
for (Entry<JsName, Integer> sizeMapEntry : sizeMap.entrySet()) {
JsName name = sizeMapEntry.getKey();
int size = sizeMapEntry.getValue();
- TypedProgramReference typedRef = typedProgramReference(name, jjsmap, obfuscateMap);
+ TypedProgramReference typedRef = typedProgramReference(name, jjsmap,
+ internedLiteralByVariableName);
writer.append(" <size " + "type=\"" + escapeXml(typedRef.type) + "\" " + "ref=\""
+ escapeXml(typedRef.description) + "\" " + "size=\"" + size + "\"/>\n");
}
@@ -200,7 +203,7 @@
}
private static TypedProgramReference typedProgramReference(JsName name,
- JavaToJavaScriptMap jjsmap, Map<JsName, String> obfuscateMap) {
+ JavaToJavaScriptMap jjsmap, Map<JsName, JsLiteral> internedLiteralByVariableName) {
JMethod method = jjsmap.nameToMethod(name);
if (method != null) {
StringBuilder sb = new StringBuilder();
@@ -231,9 +234,9 @@
return new TypedProgramReference("type", type.getName());
}
- String string = obfuscateMap.get(name);
- if (string != null) {
- return new TypedProgramReference("string", string);
+ JsLiteral literal = internedLiteralByVariableName.get(name);
+ if (literal instanceof JsStringLiteral) {
+ return new TypedProgramReference("string", ((JsStringLiteral) literal).getValue());
}
return new TypedProgramReference("var", name.getShortIdent());
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 a1448fb..2e1b3f0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -106,6 +106,7 @@
import com.google.gwt.dev.js.JsInliner;
import com.google.gwt.dev.js.JsNamespaceChooser;
import com.google.gwt.dev.js.JsNamespaceOption;
+import com.google.gwt.dev.js.JsLiteralInterner;
import com.google.gwt.dev.js.JsNormalizer;
import com.google.gwt.dev.js.JsObfuscateNamer;
import com.google.gwt.dev.js.JsPrettyNamer;
@@ -113,7 +114,6 @@
import com.google.gwt.dev.js.JsSourceGenerationVisitorWithSizeBreakdown;
import com.google.gwt.dev.js.JsStackEmulator;
import com.google.gwt.dev.js.JsStaticEval;
-import com.google.gwt.dev.js.JsStringInterner;
import com.google.gwt.dev.js.JsSymbolResolver;
import com.google.gwt.dev.js.JsUnusedFunctionRemover;
import com.google.gwt.dev.js.SizeBreakdown;
@@ -121,6 +121,7 @@
import com.google.gwt.dev.js.ast.JsForIn;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsLabel;
+import com.google.gwt.dev.js.ast.JsLiteral;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameOf;
import com.google.gwt.dev.js.ast.JsNameRef;
@@ -142,7 +143,6 @@
import com.google.gwt.soyc.SoycDashboard;
import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
-import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import org.xml.sax.SAXException;
@@ -304,7 +304,7 @@
splitJsIntoFragments(propertyOracles, permutationId, jjsmap);
// TODO(stalcup): move to optimize.
- Map<JsName, String> internedTextByVariableName = renameJsSymbols(propertyOracles);
+ Map<JsName, JsLiteral> internedLiteralByVariableName = renameJsSymbols(propertyOracles);
// TODO(stalcup): move to normalization
JsBreakUpLargeVarStatements.exec(jsProgram, propertyOracles);
@@ -325,7 +325,7 @@
// TODO(stalcup): hide metrics gathering in a callback or subclass
addSyntheticArtifacts(unifiedAst, permutation, startTimeMs, permutationId, jjsmap,
- dependenciesAndRecorder, internedTextByVariableName, isSourceMapsEnabled, jsFragments,
+ dependenciesAndRecorder, internedLiteralByVariableName, isSourceMapsEnabled, jsFragments,
sizeBreakdowns, sourceInfoMaps, permutationResult);
return permutationResult;
@@ -345,7 +345,7 @@
protected abstract void postNormalizationOptimizeJava();
- protected abstract Map<JsName, String> runDetailedNamer(PropertyOracle[] propertyOracles);
+ protected abstract Map<JsName, JsLiteral> runDetailedNamer(PropertyOracle[] propertyOracles);
protected abstract Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> splitJsIntoFragments(
PropertyOracle[] propertyOracles, int permutationId, JavaToJavaScriptMap jjsmap);
@@ -406,7 +406,8 @@
private void addSoycArtifacts(UnifiedAst unifiedAst, int permutationId,
JavaToJavaScriptMap jjsmap,
Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
- Map<JsName, String> internedTextByVariableName, String[] js, SizeBreakdown[] sizeBreakdowns,
+ Map<JsName, JsLiteral> internedLiteralByVariableName, String[] js,
+ SizeBreakdown[] sizeBreakdowns,
List<Map<Range, SourceInfo>> sourceInfoMaps, PermutationResult permutationResult,
CompilationMetricsArtifact compilationMetrics)
throws IOException, UnableToCompleteException {
@@ -419,7 +420,7 @@
} else {
permutationResult.addArtifacts(makeSoycArtifacts(permutationId, js, sizeBreakdowns,
options.isSoycExtra() ? sourceInfoMaps : null, dependenciesAndRecorder.getLeft(),
- jjsmap, internedTextByVariableName, unifiedAst.getModuleMetrics(),
+ jjsmap, internedLiteralByVariableName, unifiedAst.getModuleMetrics(),
unifiedAst.getPrecompilationMetrics(), compilationMetrics,
options.isSoycHtmlDisabled()));
}
@@ -428,14 +429,14 @@
private void addSyntheticArtifacts(UnifiedAst unifiedAst, Permutation permutation,
long startTimeMs, int permutationId, JavaToJavaScriptMap jjsmap,
Pair<SyntheticArtifact, MultipleDependencyGraphRecorder> dependenciesAndRecorder,
- Map<JsName, String> internedTextByVariableName, boolean isSourceMapsEnabled,
+ Map<JsName, JsLiteral> internedLiteralByVariableName, boolean isSourceMapsEnabled,
String[] jsFragments, SizeBreakdown[] sizeBreakdowns,
List<Map<Range, SourceInfo>> sourceInfoMaps, PermutationResult permutationResult)
throws IOException, UnableToCompleteException {
CompilationMetricsArtifact compilationMetrics = addCompilerMetricsArtifact(
unifiedAst, permutation, startTimeMs, sizeBreakdowns, permutationResult);
addSoycArtifacts(unifiedAst, permutationId, jjsmap, dependenciesAndRecorder,
- internedTextByVariableName, jsFragments, sizeBreakdowns, sourceInfoMaps,
+ internedLiteralByVariableName, jsFragments, sizeBreakdowns, sourceInfoMaps,
permutationResult, compilationMetrics);
addSourceMapArtifacts(permutationId, jjsmap, dependenciesAndRecorder, isSourceMapsEnabled,
sizeBreakdowns, sourceInfoMaps, permutationResult);
@@ -508,7 +509,8 @@
private Collection<? extends Artifact<?>> makeSoycArtifacts(int permutationId, String[] js,
SizeBreakdown[] sizeBreakdowns, List<Map<Range, SourceInfo>> sourceInfoMaps,
SyntheticArtifact dependencies, JavaToJavaScriptMap jjsmap,
- Map<JsName, String> internedTextByVariableName, ModuleMetricsArtifact moduleMetricsArtifact,
+ Map<JsName, JsLiteral> internedLiteralByVariableName,
+ ModuleMetricsArtifact moduleMetricsArtifact,
PrecompilationMetricsArtifact precompilationMetricsArtifact,
CompilationMetricsArtifact compilationMetrics, boolean htmlReportsDisabled)
throws IOException, UnableToCompleteException {
@@ -532,7 +534,8 @@
Event recordSizeMap = SpeedTracerLogger.start(
CompilerEventType.MAKE_SOYC_ARTIFACTS, "phase", "recordSizeMap");
baos.reset();
- SizeMapRecorder.recordMap(logger, baos, sizeBreakdowns, jjsmap, internedTextByVariableName);
+ SizeMapRecorder.recordMap(logger, baos, sizeBreakdowns, jjsmap,
+ internedLiteralByVariableName);
sizeMaps = new SyntheticArtifact(
SoycReportLinker.class, "stories" + permutationId + ".xml.gz", baos.toByteArray());
soycArtifacts.add(sizeMaps);
@@ -757,39 +760,43 @@
}
}
- private Map<JsName, String> renameJsSymbols(PropertyOracle[] propertyOracles) {
- Map<JsName, String> internedTextByVariableName = null;
+ private Map<JsName, JsLiteral> renameJsSymbols(PropertyOracle[] propertyOracles) {
+ Map<JsName, JsLiteral> internedLiteralByVariableName = null;
switch (options.getOutput()) {
case OBFUSCATED:
- internedTextByVariableName = runObfuscateNamer(propertyOracles);
+ internedLiteralByVariableName = runObfuscateNamer(propertyOracles);
break;
case PRETTY:
- internedTextByVariableName = runPrettyNamer(propertyOracles);
+ internedLiteralByVariableName = runPrettyNamer(propertyOracles);
break;
case DETAILED:
- internedTextByVariableName = runDetailedNamer(propertyOracles);
+ internedLiteralByVariableName = runDetailedNamer(propertyOracles);
break;
default:
throw new InternalCompilerException("Unknown output mode");
}
- return internedTextByVariableName;
+ return internedLiteralByVariableName;
}
- private Map<JsName, String> runObfuscateNamer(PropertyOracle[] propertyOracles) {
- Map<JsName, String> internedTextByVariableName;
- internedTextByVariableName = JsStringInterner.exec(jprogram, jsProgram);
+ private Map<JsName, JsLiteral> runObfuscateNamer(PropertyOracle[] propertyOracles) {
+ Map<JsName, JsLiteral> internedLiteralByVariableName =
+ JsLiteralInterner.exec(jprogram, jsProgram, JsLiteralInterner.INTERN_ALL);
FreshNameGenerator freshNameGenerator = JsObfuscateNamer.exec(jsProgram, propertyOracles);
if (options.shouldRemoveDuplicateFunctions()
&& JsStackEmulator.getStackMode(propertyOracles) == JsStackEmulator.StackMode.STRIP) {
JsDuplicateFunctionRemover.exec(jsProgram, freshNameGenerator);
}
- return internedTextByVariableName;
+ return internedLiteralByVariableName;
}
- private Map<JsName, String> runPrettyNamer(PropertyOracle[] propertyOracles) {
+ private Map<JsName, JsLiteral> runPrettyNamer(PropertyOracle[] propertyOracles) {
// We don't intern strings in pretty mode to improve readability
+ Map<JsName, JsLiteral> internedLiteralByVariableName = JsLiteralInterner.exec(
+ jprogram, jsProgram,
+ (byte) (JsLiteralInterner.INTERN_ALL & ~JsLiteralInterner.INTERN_STRINGS));
+
JsPrettyNamer.exec(jsProgram, propertyOracles);
- return Maps.newHashMap();
+ return internedLiteralByVariableName;
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java
index d9d6f93..a5dbd53 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/LibraryJavaToJavaScriptCompiler.java
@@ -45,6 +45,7 @@
import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides;
import com.google.gwt.dev.jjs.impl.codesplitter.MultipleDependencyGraphRecorder;
import com.google.gwt.dev.js.JsVerboseNamer;
+import com.google.gwt.dev.js.ast.JsLiteral;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.resource.impl.FileResource;
import com.google.gwt.dev.util.Pair;
@@ -105,7 +106,7 @@
}
@Override
- protected Map<JsName, String> runDetailedNamer(PropertyOracle[] propertyOracles) {
+ protected Map<JsName, JsLiteral> runDetailedNamer(PropertyOracle[] propertyOracles) {
JsVerboseNamer.exec(jsProgram, propertyOracles);
return null;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
index d99490d..c25fe43 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/MonolithicJavaToJavaScriptCompiler.java
@@ -46,8 +46,9 @@
import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitter;
import com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitters;
import com.google.gwt.dev.jjs.impl.codesplitter.MultipleDependencyGraphRecorder;
-import com.google.gwt.dev.js.JsStringInterner;
+import com.google.gwt.dev.js.JsLiteralInterner;
import com.google.gwt.dev.js.JsVerboseNamer;
+import com.google.gwt.dev.js.ast.JsLiteral;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.util.Pair;
import com.google.gwt.dev.util.arg.OptionOptimize;
@@ -97,8 +98,9 @@
}
@Override
- protected Map<JsName, String> runDetailedNamer(PropertyOracle[] propertyOracles) {
- Map<JsName, String> internedTextByVariableName = JsStringInterner.exec(jprogram, jsProgram);
+ protected Map<JsName, JsLiteral> runDetailedNamer(PropertyOracle[] propertyOracles) {
+ Map<JsName, JsLiteral> internedTextByVariableName =
+ JsLiteralInterner.exec(jprogram, jsProgram, JsLiteralInterner.INTERN_ALL);
JsVerboseNamer.exec(jsProgram, propertyOracles);
return internedTextByVariableName;
}
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 4dec1ff..78dc70c 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
@@ -268,9 +268,8 @@
public final List<JClassType> immortalCodeGenTypes = Lists.newArrayList();
- // TODO(rluble): (Separate compilation) the second parameter (hasWholeWorldKnoledge) must be
- // false when doing separate compilation.
public final JTypeOracle typeOracle;
+
/**
* Special serialization treatment.
*/
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
index 85bc2b6..7ff0653 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -40,7 +40,10 @@
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.ast.js.JsCastMap;
import com.google.gwt.dev.jjs.ast.js.JsCastMap.JsQueryType;
-import com.google.gwt.dev.util.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
@@ -57,7 +60,7 @@
* Replace cast and instanceof operations with calls to the Cast class. Depends
* on {@link CatchBlockNormalizer}, {@link CompoundAssignmentNormalizer},
* {@link JsoDevirtualizer}, and {@link LongCastNormalizer} having already run.
- *
+ *
* <p>
* Object and String always get a queryId of 0 and 1, respectively. The 0
* queryId always means "always succeeds". In practice, we never generate an
@@ -73,12 +76,10 @@
public class CastNormalizer {
private class AssignTypeCastabilityVisitor extends JVisitor {
- private final Set<JReferenceType> alreadyRan = new HashSet<JReferenceType>();
- private final IdentityHashMap<JReferenceType, JsCastMap> castableTypesMap =
- new IdentityHashMap<JReferenceType, JsCastMap>();
- private final List<JArrayType> instantiatedArrayTypes = new ArrayList<JArrayType>();
- private final Map<JReferenceType, Set<JReferenceType>> queriedTypes =
- new IdentityHashMap<JReferenceType, Set<JReferenceType>>();
+ private final Set<JReferenceType> alreadyRan = Sets.newHashSet();
+ private final Map<JReferenceType, JsCastMap> castableTypesMap = Maps.newIdentityHashMap();
+ private final List<JArrayType> instantiatedArrayTypes = Lists.newArrayList();
+ private final Map<JReferenceType, Set<JReferenceType>> queriedTypes = Maps.newIdentityHashMap();
{
JTypeOracle typeOracle = program.typeOracle;
@@ -275,8 +276,8 @@
}
// add an entry for me
- castableTypesMap.put(type, new JsCastMap(SourceOrigin.UNKNOWN, Lists.create(castableTypes),
- program.getJavaScriptObject()));
+ castableTypesMap.put(type, new JsCastMap(SourceOrigin.UNKNOWN,
+ ImmutableList.copyOf(castableTypes), program.getJavaScriptObject()));
}
private void recordCast(JType targetType, JExpression rhs) {
@@ -436,15 +437,15 @@
* user tried a cast that couldn't possibly work. Typically this means
* either the statically resolvable arg type is incompatible with the
* target type, or the target type was globally uninstantiable.
- *
+ *
* See {@link com.google.gwt.dev.jjs.impl.TypeTightener.TightenTypesVisitor#endVisit(JCastOperation,
* Context)}
- *
+ *
* We handle this cast by throwing a ClassCastException, unless the
* argument is null.
*/
JMethod method = program.getIndexedMethod("Cast.throwClassCastExceptionUnlessNull");
- // Note, we must update the method call to return the null type.
+ // Note, we must update the method call to return the null type.
JMethodCall call = new JMethodCall(info, null, method, toType);
call.addArg(expr);
replaceExpr = call;
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 980665d..976945a 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
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -158,10 +158,8 @@
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsVars.JsVar;
import com.google.gwt.dev.js.ast.JsWhile;
-import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.Pair;
import com.google.gwt.dev.util.StringInterner;
-import com.google.gwt.dev.util.TextOutput;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Maps;
@@ -399,16 +397,6 @@
}
@Override
- public void endVisit(JsCastMap x, Context ctx) {
- /*
- * Intern JsCastMaps, at this stage, they are only present in Array initialization,
- * so we always intern them even if they occur once, since every array initialization
- * makes a copy.
- */
- internedCastMap.add(castMapToString(x));
- }
-
- @Override
public void endVisit(JField x, Context ctx) {
String name = x.getName();
String mangleName = mangleName(x);
@@ -716,7 +704,7 @@
* might see HashSet::$add() and HashSet::add(). Logically, these methods
* should be treated equally, however they will be implemented with
* separate global functions and must be recorded independently.
- *
+ *
* Automated systems that process the symbol information can easily map
* the statically-dispatched function by looking for method names that
* begin with a dollar-sign and whose first parameter is the enclosing
@@ -753,8 +741,6 @@
private final JsName arrayLength = objectScope.declareName("length");
- private final Set<String> castMapSeen = new HashSet<String>();
-
private final Map<JClassType, JsFunction> clinitMap = new HashMap<JClassType, JsFunction>();
private JMethod currentMethod = null;
@@ -794,7 +780,7 @@
public GenerateJavaScriptVisitor(Set<JMethod> methodsForJsInlining) {
this.methodsForJsInlining = methodsForJsInlining;
}
-
+
@Override
public void endVisit(JAbsentArrayDimension x, Context ctx) {
throw new InternalCompilerException("Should not get here.");
@@ -963,16 +949,16 @@
}
}
}
-
+
// TODO(zundel): Check that each unique method has a unique
// name / poly name.
}
@Override
public void endVisit(JConditional x, Context ctx) {
- JsExpression elseExpr = (JsExpression) pop(); // elseExpr
- JsExpression thenExpr = (JsExpression) pop(); // thenExpr
- JsExpression ifTest = (JsExpression) pop(); // ifTest
+ JsExpression elseExpr = pop(); // elseExpr
+ JsExpression thenExpr = pop(); // thenExpr
+ JsExpression ifTest = pop(); // ifTest
push(new JsConditional(x.getSourceInfo(), ifTest, thenExpr, elseExpr));
}
@@ -1302,7 +1288,7 @@
* as Java, so it's okay to just predeclare all local vars at the top of
* the function, which saves us having to use the "var" keyword over and
* over.
- *
+ *
* Note: it's fine to use the same JS ident to represent two different
* Java locals of the same name since they could never conflict with each
* other in Java. We use the alreadySeen set to make sure we don't declare
@@ -1424,12 +1410,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)));
@@ -1473,8 +1459,7 @@
// Long lits must go at the top, they can be constant field initializers.
generateLongLiterals(vars);
generateImmortalTypes(vars);
- generateInternedCastMapLiterals(vars);
-
+
// Class objects, but only if there are any.
if (x.getDeclaredTypes().contains(x.getTypeClassLiteralHolder())) {
// TODO: perhaps they could be constant field initializers also?
@@ -1520,30 +1505,7 @@
super.endVisit(x, ctx);
JsArrayLiteral arrayLit = (JsArrayLiteral) pop();
SourceInfo sourceInfo = x.getSourceInfo();
- String stringMap = castMapToString(x);
- // if interned, use variable reference
- if (namesByCastMap.containsKey(stringMap)) {
- push(namesByCastMap.get(stringMap).makeRef(x.getSourceInfo()));
- } else if (internedCastMap.contains(stringMap)) {
- // interned variable hasn't been created yet
- String internName = "CM$";
- boolean first = true;
- for (JExpression expr : x.getExprs()) {
- if (first) {
- first = false;
- } else {
- internName += "_";
- }
- // Name is CM$queryId_queryId_queryId
- internName += ((JsQueryType) expr).getQueryId();
- }
- JsName internedCastMapName = topScope.declareName(internName, internName);
- namesByCastMap.put(stringMap, internedCastMapName);
- castMapByString.put(stringMap, castMapToObjectLiteral(arrayLit, sourceInfo));
- push(internedCastMapName.makeRef(x.getSourceInfo()));
- } else {
- push(castMapToObjectLiteral(arrayLit, sourceInfo));
- }
+ push(castMapToObjectLiteral(arrayLit, sourceInfo));
}
@Override
@@ -1569,8 +1531,8 @@
@Override
public void endVisit(JsonPropInit init, Context ctx) {
- JsExpression valueExpr = (JsExpression) pop();
- JsExpression labelExpr = (JsExpression) pop();
+ JsExpression valueExpr = pop();
+ JsExpression labelExpr = pop();
push(new JsPropertyInitializer(init.getSourceInfo(), labelExpr, valueExpr));
}
@@ -1589,7 +1551,7 @@
JsTry jsTry = new JsTry(x.getSourceInfo());
if (x.getFinallyBlock() != null) {
- JsBlock finallyBlock = (JsBlock) pop(); // finallyBlock
+ JsBlock finallyBlock = pop(); // finallyBlock
if (finallyBlock.getStatements().size() > 0) {
jsTry.setFinallyBlock(finallyBlock);
}
@@ -1598,7 +1560,7 @@
int size = x.getCatchClauses().size();
assert (size < 2);
if (size == 1) {
- JsBlock catchBlock = (JsBlock) pop(); // catchBlocks
+ JsBlock catchBlock = pop(); // catchBlocks
pop(); // catchArgs
JsCatch jsCatch = catchMap.get(x.getCatchClauses().get(0).getBlock());
jsCatch.setBody(catchBlock);
@@ -1643,7 +1605,7 @@
if (x.getSuperClass() != null && !alreadyRan.contains(x)) {
accept(x.getSuperClass());
}
-
+
return super.visit(x, ctx);
}
@@ -1675,11 +1637,6 @@
entryMethodToIndex.put(entryMethods.get(i), i);
}
- for (JDeclaredType type : x.getDeclaredTypes()) {
- if (program.typeOracle.isInstantiatedType(type)) {
- internCastMap(program.getCastMap(type));
- }
- }
return true;
}
@@ -1848,6 +1805,7 @@
private JsObjectLiteral castMapToObjectLiteral(JsArrayLiteral arrayLit, SourceInfo sourceInfo) {
JsObjectLiteral objLit = new JsObjectLiteral(sourceInfo);
+ objLit.setInternable();
List<JsPropertyInitializer> props = objLit.getPropertyInitializers();
JsNumberLiteral one = new JsNumberLiteral(sourceInfo, 1);
for (JsExpression expr : arrayLit.getExpressions()) {
@@ -2200,16 +2158,6 @@
}
}
- private void generateInternedCastMapLiterals(JsVars vars) {
- SourceInfo info = vars.getSourceInfo();
- int id = 0;
- for (Map.Entry<String, JsName> castMapEntry : namesByCastMap.entrySet()) {
- JsVar var = new JsVar(info, castMapEntry.getValue());
- var.setInitExpr(castMapByString.get(castMapEntry.getKey()));
- vars.add(var);
- }
- }
-
private void generateLongLiterals(JsVars vars) {
for (Entry<Long, JsName> entry : longLits.entrySet()) {
JsName jsName = entry.getValue();
@@ -2238,7 +2186,7 @@
superSeed));
JsExpression castMap = generateCastableTypeMap(x);
defineSeed.getArguments().add(castMap);
-
+
// Chain assign the same prototype to every live constructor.
for (JMethod method : x.getMethods()) {
if (liveCtors.contains(method)) {
@@ -2358,15 +2306,6 @@
statements.add(0, asg.makeStmt());
}
- private void internCastMap(JsCastMap x) {
- String stringMap = castMapToString(x);
- if (castMapSeen.contains(stringMap)) {
- internedCastMap.add(stringMap);
- } else {
- castMapSeen.add(stringMap);
- }
- }
-
private JsInvocation maybeCreateClinitCall(JField x) {
if (!x.isStatic()) {
return null;
@@ -2413,12 +2352,12 @@
* If a field is a literal, we can potentially treat it as immutable and assign it once on the
* prototype, to be reused by all instances of the class, instead of re-assigning the same
* literal in each constructor.
- *
+ *
* Technically, to match JVM semantics, we should only do this for final or static fields. For
* non-final/non-static fields, a super class's cstr, when it calls a polymorphic method that is
* overridden in the subclass, should actually see default values (not the literal initializer)
* before the subclass's cstr runs.
- *
+ *
* However, cstr's calling polymorphic methods is admittedly an uncommon case, so we apply some
* heuristics to see if we can initialize the field on the prototype anyway.
*/
@@ -2502,7 +2441,7 @@
/**
* Determines which classes can potentially see uninitialized values of their subclasses' fields.
- *
+ *
* If a class can not observe subclass uninitialized fields then the initialization of those could
* be hoisted to the prototype.
*/
@@ -2709,8 +2648,6 @@
*/
private final JsScope interfaceScope;
- private final Set<String> internedCastMap = new HashSet<String>();
-
private final JsProgram jsProgram;
private final Set<JConstructor> liveCtors = new IdentityHashSet<JConstructor>();
@@ -2732,7 +2669,6 @@
new IdentityHashMap<JAbstractMethodBody, JsFunction>();
private final Map<HasName, JsName> names = new IdentityHashMap<HasName, JsName>();
private int nextSeedId = 1;
- private Map<String, JsName> namesByCastMap = new HashMap<String, JsName>();
private JsFunction nullFunc;
/**
@@ -2852,18 +2788,6 @@
getSeedId(program.getTypeJavaLangString());
}
- String castMapToString(JsCastMap x) {
- if (x == null || x.getExprs() == null || x.getExprs().size() == 0) {
- return "{}";
- } else {
- TextOutput textOutput = new DefaultTextOutput(true);
- ToStringGenerationVisitor toStringer = new ToStringGenerationVisitor(textOutput);
- toStringer.accept(x);
- String stringMap = textOutput.toString();
- return stringMap;
- }
- }
-
String getNameString(HasName hasName) {
String s = hasName.getName().replaceAll("_", "_1").replace('.', '_');
return s;
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 22ebc87..9c79f17 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsHoister.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsHoister.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -173,7 +173,7 @@
public void endVisit(JsNumberLiteral x, JsContext ctx) {
stack.push(x);
}
-
+
@Override
public void endVisit(JsNumericEntry x, JsContext ctx) {
stack.push(x);
@@ -185,6 +185,10 @@
List<JsPropertyInitializer> inits = toReturn.getPropertyInitializers();
int size = x.getPropertyInitializers().size();
+ if (x.isInternable()) {
+ toReturn.setInternable();
+ }
+
while (size-- > 0) {
/*
* JsPropertyInitializers are the only non-JsExpression objects that we
@@ -195,7 +199,11 @@
JsPropertyInitializer newInit = new JsPropertyInitializer(
x.getSourceInfo());
newInit.setValueExpr(stack.pop());
- newInit.setLabelExpr(stack.pop());
+ if (successful) {
+ newInit.setLabelExpr(stack.pop());
+ } else {
+ stack.pop();
+ }
inits.add(0, newInit);
}
@@ -251,7 +259,7 @@
* caller. This does not perform any name replacement, nor does it verify the
* scope of referenced elements, but simply constructs a mutable copy of the
* expression that can be manipulated at-will.
- *
+ *
* @return A copy of the original expression, or <code>null</code> if the
* expression cannot be hoisted.
*/
diff --git a/dev/core/src/com/google/gwt/dev/js/JsLiteralInterner.java b/dev/core/src/com/google/gwt/dev/js/JsLiteralInterner.java
new file mode 100644
index 0000000..0b6ede6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsLiteralInterner.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2008 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;
+
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.js.ast.JsArrayLiteral;
+import com.google.gwt.dev.js.ast.JsBinaryOperation;
+import com.google.gwt.dev.js.ast.JsBlock;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsLiteral;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNode;
+import com.google.gwt.dev.js.ast.JsNumberLiteral;
+import com.google.gwt.dev.js.ast.JsObjectLiteral;
+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.JsRegExp;
+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 com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+import com.google.gwt.thirdparty.guava.common.collect.HashMultiset;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Multiset;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Interns conditionally either all literals in a JsProgram, or literals
+ * which exceed a certain usage count. Each unique literal will be assigned to a
+ * variable in an appropriate program fragment and the JsLiteral will be
+ * replaced with a JsNameRef. This optimization is complete in a single pass,
+ *
+ * It is not safe to run the interner multiple times on the same tree as the names that are
+ * assigned to interned literals will collide.
+ */
+public class JsLiteralInterner {
+
+ /**
+ * Counts occurrences of each potentially internable literal.
+ */
+ private static class OccurrenceCounterVisitor extends JsVisitor {
+
+ private Multiset<JsLiteral> countByLiteral = HashMultiset.create();
+
+ public Multiset<JsLiteral> getLiteralCounts() {
+ return countByLiteral;
+ }
+
+ /**
+ * Implement visit(Js*Literal,...) in a general way as there is no visit(JsLiteral, JsContext)
+ * to override in Js*Visitor.
+ */
+ private boolean doVisitLiteral(JsLiteral x) {
+ if (x.isInternable()) {
+ countByLiteral.add(x);
+ // Literal was internable and counted, do not count its internal literals.
+ return false;
+ }
+ // The literal was not internable but might have some internable constants inside,
+ // so count them.
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsBinaryOperation x, JsContext ctx) {
+ if (!hasLhsLiteral(x)) {
+ // Literal l-values should not arise from valid code, but they are excluded
+ // anyway so that errors are not masked away by interning.
+ accept(x.getArg1());
+ }
+ accept(x.getArg2());
+ return false;
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsPostfixOperation x, JsContext ctx) {
+ return !(x.getArg() instanceof JsLiteral);
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsPrefixOperation x, JsContext ctx) {
+ return !(x.getArg() instanceof JsLiteral);
+ }
+
+ /**
+ * We ignore property initializer labels in object literals, but do process
+ * the expression. This is because the LHS is always treated as a string,
+ * and never evaluated as an expression.
+ */
+ @Override
+ public boolean visit(JsPropertyInitializer x, JsContext ctx) {
+ accept(x.getValueExpr());
+ return false;
+ }
+
+ /**
+ * Count occurences of String literal.
+ */
+ @Override
+ public boolean visit(JsStringLiteral x, JsContext ctx) {
+ return doVisitLiteral(x);
+ }
+
+ /**
+ * Count occurences of Object literal.
+ */
+ @Override
+ public boolean visit(JsObjectLiteral x, JsContext ctx) {
+ return doVisitLiteral(x);
+ }
+
+ @Override
+ public boolean visit(JsRegExp x, JsContext ctx) {
+ return doVisitLiteral(x);
+ }
+
+ @Override
+ public boolean visit(JsNumberLiteral x, JsContext ctx) {
+ return doVisitLiteral(x);
+ }
+
+ /**
+ * Count occurences of Array literal.
+ */
+ @Override
+ public boolean visit(JsArrayLiteral x, JsContext ctx) {
+ return doVisitLiteral(x);
+ }
+
+ /**
+ * This prevents duplicating the intern pool by not traversing JsVar
+ * declarations that look like they were created by the interner.
+ */
+ @Override
+ public boolean visit(JsVar x, JsContext ctx) {
+ return !(x.getName().getIdent().startsWith(PREFIX));
+ }
+ }
+
+ /**
+ * Replaces internable JsLiterals with JsNameRefs, creating new JsName allocations
+ * on the fly.
+ */
+ private static class LiteralInterningVisitor extends JsModVisitor {
+ /*
+ * Minimum number of times a literal must occur to be interned.
+ */
+ private static final Integer INTERN_THRESHOLD = Integer.parseInt(
+ // TODO(rluble): change the property name to reflect that not only strings are interned.
+ System.getProperty("gwt.jjs.stringInternerThreshold", "2"));
+
+ /**
+ * 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 Map<JsLiteral, Integer> fragmentAssignment = Maps.newLinkedHashMap();
+
+ /**
+ * A counter used for assigning ids to literals. Even though it's unlikely
+ * that someone would actually have two billion literals in their
+ * application, it doesn't hurt to think ahead.
+ */
+ private long lastId = 0;
+
+ /**
+ * Count of # of occurences of each literal, or null if
+ * count-sensitive interning is off.
+ */
+ private Multiset<JsLiteral> occurrenceMap;
+
+ /**
+ * Only used to get fragment load order so literals used in multiple
+ * fragments are placed in the right fragment.
+ */
+ private final JProgram program;
+
+ /**
+ * Records the scope in which the interned identifiers are declared.
+ */
+ private final JsScope scope;
+
+ /**
+ * This is a TreeMap to ensure consistent iteration order.
+ */
+ private final Map<JsLiteral, JsName> toCreate = Maps.newLinkedHashMap();
+
+ /**
+ * This is a set of flags indicating what types of literals are to be interned.
+ */
+ private final byte whatToIntern;
+
+ /**
+ * Constructor.
+ *
+ * @param scope specifies the scope in which the interned literals should be.
+ * @param occurrenceMap a multiset representing the literal counts.
+ * @param whatToIntern what types of literals are to be interned.
+ */
+ public LiteralInterningVisitor(JProgram program, JsScope scope,
+ Multiset<JsLiteral> occurrenceMap, byte whatToIntern) {
+ this.program = program;
+ this.scope = scope;
+ this.occurrenceMap = occurrenceMap;
+ this.whatToIntern = whatToIntern;
+ }
+
+ @Override
+ public void endVisit(JsProgramFragment x, JsContext ctx) {
+ currentFragment++;
+ }
+
+ /**
+ * Replace JsArrayLiteral instances with JsNameRefs.
+ */
+ @Override
+ public boolean visit(JsArrayLiteral x, JsContext ctx) {
+ boolean interned = false;
+ if ((whatToIntern & INTERN_ARRAY_LITERALS) != 0) {
+ interned = maybeInternLiteral(x, ctx);
+ }
+
+ // If the array literal is interned do not try to intern any of its contents.
+ return !interned;
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsBinaryOperation x, JsContext ctx) {
+ if (!hasLhsLiteral(x)) {
+ // Literal l-values should not arise from valid code, but they are excluded
+ // anyway so that errors are not masked away by interning.
+ x.setArg1(accept(x.getArg1()));
+ }
+ x.setArg2(accept(x.getArg2()));
+ return false;
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsPostfixOperation x, JsContext ctx) {
+ return !(x.getArg() instanceof JsLiteral);
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsPrefixOperation x, JsContext ctx) {
+ return !(x.getArg() instanceof JsLiteral);
+ }
+
+ /**
+ * We ignore property initializer labels in object literals, but do process
+ * the expression. This is because the LHS is always treated as a string,
+ * and never evaluated as an expression.
+ */
+ @Override
+ public boolean visit(JsPropertyInitializer x, JsContext ctx) {
+ x.setValueExpr(accept(x.getValueExpr()));
+ return false;
+ }
+
+ /**
+ * Replace JsStringLiteral instances with JsNameRefs.
+ */
+ @Override
+ public boolean visit(JsStringLiteral x, JsContext ctx) {
+ if ((whatToIntern & INTERN_STRINGS) != 0) {
+ maybeInternLiteral(x, ctx);
+ }
+ return false;
+ }
+
+ /**
+ * Replace JsObjectLiteral instances with JsNameRefs.
+ */
+ @Override
+ public boolean visit(JsObjectLiteral x, JsContext ctx) {
+ boolean interned = false;
+ if ((whatToIntern & INTERN_OBJECT_LITERALS) != 0) {
+ interned = maybeInternLiteral(x, ctx);
+ }
+
+ // If the object literal is interned do not try to intern any of its contents.
+ return !interned;
+ }
+
+ /**
+ * Replace JsRegExp instances with JsNameRefs.
+ */
+ @Override
+ public boolean visit(JsRegExp x, JsContext ctx) {
+ if ((whatToIntern & INTERN_REGEXES) != 0) {
+ maybeInternLiteral(x, ctx);
+ }
+ return false;
+ }
+
+ /**
+ * Replace JsNumberLiteral instances with JsNameRefs.
+ */
+ @Override
+ public boolean visit(JsNumberLiteral x, JsContext ctx) {
+ if ((whatToIntern & INTERN_NUMBERS) != 0) {
+ maybeInternLiteral(x, ctx);
+ }
+ return false;
+ }
+
+ private boolean maybeInternLiteral(JsLiteral x, JsContext ctx) {
+ if (!x.isInternable()) {
+ return false;
+ }
+
+ if (occurrenceMap != null) {
+ int occurrences = occurrenceMap.count(x);
+ if (occurrences < INTERN_THRESHOLD) {
+ return false;
+ }
+ }
+
+ JsName name = toCreate.get(x);
+ if (name == null) {
+ String ident = PREFIX + lastId++;
+ name = scope.declareName(ident);
+ 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
+ Preconditions.checkState(program != null, "JsLiteralInterner cannot be used with "
+ + "fragmented JsProgram without an accompanying JProgram");
+
+ int newAssignment = program.getCommonAncestorFragmentId(currentAssignment, currentFragment);
+ if (newAssignment != currentAssignment) {
+ // Assign the JsName to the common ancestor.
+ fragmentAssignment.put(x, newAssignment);
+ }
+ }
+
+ ctx.replaceMe(name.makeRef(x.getSourceInfo().makeChild()));
+ return true;
+ }
+
+ /**
+ * This prevents duplicating the intern pool by not traversing JsVar
+ * declarations that look like they were created by the interner.
+ */
+ @Override
+ public boolean visit(JsVar x, JsContext ctx) {
+ return !(x.getName().getIdent().startsWith(PREFIX));
+ }
+ }
+
+ /**
+ * Flags to control what type of literals to intern.
+ */
+ public static final byte INTERN_ARRAY_LITERALS = 0x01;
+ public static final byte INTERN_NUMBERS = 0x02;
+ public static final byte INTERN_OBJECT_LITERALS = 0x04;
+ public static final byte INTERN_REGEXES = 0x08;
+ public static final byte INTERN_STRINGS = 0x10;
+ public static final byte INTERN_ALL = INTERN_ARRAY_LITERALS | INTERN_NUMBERS |
+ INTERN_OBJECT_LITERALS | INTERN_REGEXES | INTERN_STRINGS;
+
+ private static final String PREFIX = "$intern_";
+
+ /**
+ * Apply interning of literals to a JsProgram. The symbol names for the
+ * interned literals 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}
+ * @param program the JsProgram
+ * @param whatToIntern a byte mask indicating what types of literals are interned.
+ * @return a map describing the interning that occurred
+ */
+ public static Map<JsName, JsLiteral> exec(JProgram jprogram, JsProgram program,
+ byte whatToIntern) {
+ LiteralInterningVisitor v = new LiteralInterningVisitor(jprogram, program.getScope(),
+ computeOccurrenceCounts(program), whatToIntern);
+ v.accept(program);
+
+ Map<Integer, Set<JsLiteral>> bins = Maps.newHashMap();
+ for (int i = 0, j = program.getFragmentCount(); i < j; i++) {
+ bins.put(i, Sets.<JsLiteral>newLinkedHashSet());
+ }
+ for (Map.Entry<JsLiteral, Integer> entry : v.fragmentAssignment.entrySet()) {
+ Set<JsLiteral> set = bins.get(entry.getValue());
+ assert set != null;
+ set.add(entry.getKey());
+ }
+
+ for (Map.Entry<Integer, Set<JsLiteral>> entry : bins.entrySet()) {
+ createVars(program, program.getFragmentBlock(entry.getKey()),
+ entry.getValue(), v.toCreate);
+ }
+
+ return reverse(v.toCreate);
+ }
+
+ /**
+ * Intern literals that occur within a JsBlock. The symbol declarations
+ * will be added as the first statement in the block.
+ *
+ * @param block the block to visit.
+ * @param scope the JsScope in which to reserve the new identifiers.
+ * @param alwaysIntern whether to intern all literals regardless of their occurrence count.
+ * @return {@code true} if any changes were made to the block.
+ */
+ public static boolean exec(JsProgram program, JsBlock block, JsScope scope,
+ boolean alwaysIntern) {
+ LiteralInterningVisitor v = new LiteralInterningVisitor(null, scope, alwaysIntern ? null :
+ computeOccurrenceCounts(block), INTERN_ALL);
+ v.accept(block);
+
+ createVars(program, block, v.toCreate.keySet(), v.toCreate);
+
+ return v.didChange();
+ }
+
+ /**
+ * Create variable declarations in {@code block} for literals
+ * {@code toCreate} using the variable map {@code names}.
+ */
+ private static void createVars(JsProgram program, JsBlock block,
+ Collection<JsLiteral> toCreate, Map<JsLiteral, JsName> names) {
+ if (toCreate.size() > 0) {
+ // Create the pool of variable names.
+ SourceInfo sourceInfo = program.createSourceInfoSynthetic(JsLiteralInterner.class);
+ JsVars vars = new JsVars(sourceInfo);
+ for (JsLiteral literal : toCreate) {
+ JsVar var = new JsVar(sourceInfo, names.get(literal));
+ var.setInitExpr(literal);
+ vars.add(var);
+ }
+ block.getStatements().add(0, vars);
+ }
+ }
+
+ private static Multiset<JsLiteral> computeOccurrenceCounts(JsNode node) {
+ OccurrenceCounterVisitor oc = new OccurrenceCounterVisitor();
+ oc.accept(node);
+ return oc.getLiteralCounts();
+ }
+
+ private static Map<JsName, JsLiteral> reverse(
+ Map<JsLiteral, JsName> toCreate) {
+ Map<JsName, JsLiteral> reversed = Maps.newLinkedHashMap();
+ for (Entry<JsLiteral, JsName> entry : toCreate.entrySet()) {
+ reversed.put(entry.getValue(), entry.getKey());
+ }
+ return reversed;
+ }
+
+ private static boolean hasLhsLiteral(JsBinaryOperation x) {
+ return x.getOperator().isAssignment()
+ && (x.getArg1() instanceof JsLiteral);
+ }
+
+ /**
+ * Utility class.
+ */
+ private JsLiteralInterner() {
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java b/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java
index 33a7233..114be3f 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -48,6 +48,7 @@
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.JsPropertyInitializer;
import com.google.gwt.dev.js.ast.JsReturn;
import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.js.ast.JsStatement;
@@ -72,7 +73,7 @@
/**
* Emulates the JS stack in order to provide useful stack traces on browers that
* do not provide useful stack information.
- *
+ *
* @see com.google.gwt.core.client.impl.StackTraceCreator
*/
public class JsStackEmulator {
@@ -143,14 +144,14 @@
* the function.
* <p>
* General stack depth entry/exit code:
- *
+ *
* <pre>
* function foo() {
* var stackIndex;
* $stack[stackIndex = ++$stackDepth] = foo;
- *
+ *
* ... do stuff ..
- *
+ *
* $stackDepth = stackIndex - 1;
* }
* </pre>
@@ -159,7 +160,7 @@
* blocks with as associated finally block, it is necessary to introduce a
* local variable to indicate if control flow is expected to terminate
* normally at the end of the finally block:
- *
+ *
* <pre>
* var exitingEarly;
* try {
@@ -181,7 +182,7 @@
* reset to the local stack index value. This allows browser-native exceptions
* to be created with the correct stack trace before the finally code is
* executed with a correct stack depth.
- *
+ *
* <pre>
* try {
* foo();
@@ -189,9 +190,9 @@
* bar();
* }
* </pre>
- *
+ *
* becomes
- *
+ *
* <pre>
* try {
* foo();
@@ -393,7 +394,7 @@
* block. A no-op if the last statement is a <code>throw</code> or
* <code>return</code> statement, since it will have already caused a pop
* statement to have been added.
- *
+ *
* @param checkEarlyExit if <code>true</code>, generates
* <code>earlyExit && pop()</code>
*/
@@ -478,7 +479,7 @@
/**
* Pops the stack frame.
- *
+ *
* @param x the statement that will cause the pop
* @param ctx the visitor context
*/
@@ -594,11 +595,11 @@
* <p>
* This simply generates code to set entries in the <code>$location</code>
* stack, parallel to <code>$stack</code>:
- *
+ *
* <pre>
* ($location[stackIndex] = 'Foo.java:' + 42, expr);
* </pre>
- *
+ *
* Inclusion of file names is dependent on the value of the
* {@link JsStackEmulator#recordFileNames} field.
*/
@@ -620,6 +621,13 @@
}
@Override
+ public boolean visit(JsPropertyInitializer x, JsContext ctx) {
+ // do not instrument left hand side of initializer.
+ x.setValueExpr(accept(x.getValueExpr()));
+ return false;
+ }
+
+ @Override
public void endVisit(JsArrayAccess x, JsContext ctx) {
record(x, ctx);
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java b/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java
deleted file mode 100644
index 98892de..0000000
--- a/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright 2008 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;
-
-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;
-import com.google.gwt.dev.js.ast.JsModVisitor;
-import com.google.gwt.dev.js.ast.JsName;
-import com.google.gwt.dev.js.ast.JsNode;
-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 com.google.gwt.dev.js.ast.JsVisitor;
-import com.google.gwt.thirdparty.guava.common.base.Preconditions;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-/**
- * Interns conditionally either all String literals in a JsProgram, or Strings
- * which exceed a certain usage count. Each unique String will be 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 {
-
- /**
- * Counts occurences of each potentially internable String literal.
- */
- private static class OccurenceCounter extends JsVisitor {
-
- private Map<String, Integer> occurenceMap = new HashMap<String, Integer>();
-
- public Map<String, Integer> buildOccurenceMap() {
- return occurenceMap;
- }
-
- /**
- * Prevents 'fixing' an otherwise illegal operation.
- */
- @Override
- public boolean visit(JsBinaryOperation x, JsContext ctx) {
- return !x.getOperator().isAssignment()
- || !(x.getArg1() instanceof JsStringLiteral);
- }
-
- /**
- * Prevents 'fixing' an otherwise illegal operation.
- */
- @Override
- public boolean visit(JsPostfixOperation x, JsContext ctx) {
- return !(x.getArg() instanceof JsStringLiteral);
- }
-
- /**
- * Prevents 'fixing' an otherwise illegal operation.
- */
- @Override
- public boolean visit(JsPrefixOperation x, JsContext ctx) {
- return !(x.getArg() instanceof JsStringLiteral);
- }
-
- /**
- * We ignore property initializer labels in object literals, but do process
- * the expression. This is because the LHS is always treated as a string,
- * and never evaluated as an expression.
- */
- @Override
- public boolean visit(JsPropertyInitializer x, JsContext ctx) {
- accept(x.getValueExpr());
- return false;
- }
-
- /**
- * Count occurences of String literal.
- */
- @Override
- public boolean visit(JsStringLiteral x, JsContext ctx) {
- String literal = x.getValue();
- Integer occurs = occurenceMap.get(literal);
- if (occurs == null) {
- occurs = 0;
- }
- occurenceMap.put(literal, ++occurs);
- return false;
- }
-
- /**
- * This prevents duplicating the intern pool by not traversing JsVar
- * declarations that look like they were created by the interner.
- */
- @Override
- public boolean visit(JsVar x, JsContext ctx) {
- return !(x.getName().getIdent().startsWith(PREFIX));
- }
- }
-
- /**
- * Replaces JsStringLiterals with JsNameRefs, creating new JsName allocations
- * on the fly.
- */
- private static class StringVisitor extends JsModVisitor {
- /*
- * Minimum number of times a string must occur to be interned.
- */
- private static final Integer INTERN_THRESHOLD = Integer.parseInt(
- System.getProperty("gwt.jjs.stringInternerThreshold", "2"));
-
- /**
- * 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.
- */
- private long lastId = 0;
-
- /**
- * Count of # of occurences of each String literal, or null if
- * count-sensitive interning is off.
- */
- private Map<String, Integer> occurenceMap;
-
- /**
- * 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;
-
- /**
- * This is a TreeMap to ensure consistent iteration order, based on the
- * lexicographical ordering of the string constant.
- */
- private final SortedMap<JsStringLiteral, JsName> toCreate = new TreeMap<JsStringLiteral, JsName>(
- LITERAL_COMPARATOR);
-
- /**
- * Constructor.
- *
- * @param scope specifies the scope in which the interned strings should be
- * @param occurenceMap
- */
- public StringVisitor(JProgram program, JsScope scope,
- Map<String, Integer> occurenceMap) {
- this.program = program;
- this.scope = scope;
- this.occurenceMap = occurenceMap;
- }
-
- @Override
- public void endVisit(JsProgramFragment x, JsContext ctx) {
- currentFragment++;
- }
-
- /**
- * Prevents 'fixing' an otherwise illegal operation.
- */
- @Override
- public boolean visit(JsBinaryOperation x, JsContext ctx) {
- return !x.getOperator().isAssignment()
- || !(x.getArg1() instanceof JsStringLiteral);
- }
-
- /**
- * Prevents 'fixing' an otherwise illegal operation.
- */
- @Override
- public boolean visit(JsPostfixOperation x, JsContext ctx) {
- return !(x.getArg() instanceof JsStringLiteral);
- }
-
- /**
- * Prevents 'fixing' an otherwise illegal operation.
- */
- @Override
- public boolean visit(JsPrefixOperation x, JsContext ctx) {
- return !(x.getArg() instanceof JsStringLiteral);
- }
-
- /**
- * We ignore property initializer labels in object literals, but do process
- * the expression. This is because the LHS is always treated as a string,
- * and never evaluated as an expression.
- */
- @Override
- public boolean visit(JsPropertyInitializer x, JsContext ctx) {
- x.setValueExpr(accept(x.getValueExpr()));
- return false;
- }
-
- /**
- * Replace JsStringLiteral instances with JsNameRefs.
- */
- @Override
- public boolean visit(JsStringLiteral x, JsContext ctx) {
- if (occurenceMap != null) {
- Integer occurences = occurenceMap.get(x.getValue());
- assert occurences != null;
- if (occurences < INTERN_THRESHOLD) {
- return false;
- }
- }
- JsName name = toCreate.get(x);
- if (name == null) {
- String ident = PREFIX + lastId++;
- name = scope.declareName(ident);
- 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
- Preconditions.checkState(program != null, "JsStringInterner cannot be used with "
- + "fragmented JsProgram without an accompanying JProgram");
-
- // TODO(rluble): assumes only one leftovers. Needs to change to accommodate multiple
- // leftovers.
- int newAssignment = program.getCommonAncestorFragmentId(currentAssignment, currentFragment);
- if (newAssignment != currentAssignment) {
- // Assign the JsName to the common ancestor.
- fragmentAssignment.put(x, newAssignment);
- }
- }
-
- ctx.replaceMe(name.makeRef(x.getSourceInfo().makeChild()));
- return false;
- }
-
- /**
- * This prevents duplicating the intern pool by not traversing JsVar
- * declarations that look like they were created by the interner.
- */
- @Override
- public boolean visit(JsVar x, JsContext ctx) {
- return !(x.getName().getIdent().startsWith(PREFIX));
- }
- }
-
- public static final String PREFIX = "$intern_";
-
- private static final Comparator<JsStringLiteral> LITERAL_COMPARATOR =
- new Comparator<JsStringLiteral>() {
- public int compare(JsStringLiteral o1, JsStringLiteral o2) {
- return o1.getValue().compareTo(o2.getValue());
- }
- };
-
- /**
- * 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
- * @return a map describing the interning that occurred
- */
- public static Map<JsName, String> exec(JProgram jprogram, JsProgram program) {
- StringVisitor v = new StringVisitor(jprogram, program.getScope(), getOccurenceMap(program));
- 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);
- }
-
- return reverse(v.toCreate);
- }
-
- /**
- * Intern String literals that occur within a JsBlock. The symbol declarations
- * will be added as the first statement in the block.
- *
- * @param block the block to visit
- * @param scope the JsScope in which to reserve the new identifiers
- * @param alwaysIntern true for browsers like IE which must always intern literals
- * @return <code>true</code> if any changes were made to the block
- */
- public static boolean exec(JsProgram program, JsBlock block, JsScope scope,
- boolean alwaysIntern) {
- StringVisitor v = new StringVisitor(null, scope, alwaysIntern ? null :
- getOccurenceMap(block));
- v.accept(block);
-
- createVars(program, block, v.toCreate.keySet(), v.toCreate);
-
- return v.didChange();
- }
-
- /**
- * Create variable declarations in <code>block</code> for literal strings
- * <code>toCreate</code> using the variable map <code>names</code>.
- */
- private static void createVars(JsProgram program, JsBlock block,
- Collection<JsStringLiteral> toCreate, Map<JsStringLiteral, JsName> names) {
- if (toCreate.size() > 0) {
- // Create the pool of variable names.
- SourceInfo sourceInfo = program.createSourceInfoSynthetic(JsStringInterner.class);
- JsVars vars = new JsVars(sourceInfo);
- for (JsStringLiteral literal : toCreate) {
- JsVar var = new JsVar(sourceInfo, names.get(literal));
- var.setInitExpr(literal);
- vars.add(var);
- }
- block.getStatements().add(0, vars);
- }
- }
-
- private static Map<String, Integer> getOccurenceMap(JsNode node) {
- OccurenceCounter oc = new OccurenceCounter();
- oc.accept(node);
- return oc.buildOccurenceMap();
- }
-
- private static Map<JsName, String> reverse(
- SortedMap<JsStringLiteral, JsName> toCreate) {
- Map<JsName, String> reversed = new LinkedHashMap<JsName, String>(
- toCreate.size());
- for (Entry<JsStringLiteral, JsName> entry : toCreate.entrySet()) {
- reversed.put(entry.getValue(), entry.getKey().getValue());
- }
- return reversed;
- }
-
- /**
- * Utility class.
- */
- private JsStringInterner() {
- }
-}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java b/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java
index dc04ee0..548d88c 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java
@@ -1,11 +1,11 @@
/*
* Copyright 2008 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
@@ -25,6 +25,8 @@
private final List<JsExpression> exprs = new ArrayList<JsExpression>();
+ private boolean internable = false;
+
public JsArrayLiteral(SourceInfo sourceInfo) {
super(sourceInfo);
}
@@ -34,11 +36,25 @@
}
@Override
+ public boolean equals(Object that) {
+ if (that == null && this.getClass() != that.getClass()) {
+ return false;
+ }
+ JsArrayLiteral thatLiteral = (JsArrayLiteral) that;
+ return internable == thatLiteral.internable && exprs.equals(thatLiteral.exprs);
+ }
+
+ @Override
public NodeKind getKind() {
return NodeKind.ARRAY;
}
@Override
+ public int hashCode() {
+ return exprs.hashCode() + 17 * (internable ? 0 : 1);
+ }
+
+ @Override
public boolean hasSideEffects() {
for (JsExpression expr : exprs) {
if (expr.hasSideEffects()) {
@@ -75,4 +91,17 @@
}
v.endVisit(this, ctx);
}
+
+ /**
+ * Some array literals are not mutated and hence internable.
+ */
+ @Override
+ public boolean isInternable() {
+ // Not profitable to intern empty arrays.
+ return internable && exprs.size() > 0;
+ }
+
+ public void setInternable() {
+ internable = true;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsLiteral.java b/dev/core/src/com/google/gwt/dev/js/ast/JsLiteral.java
index 9fdb8ce..ab5c303 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsLiteral.java
@@ -1,11 +1,11 @@
/*
* Copyright 2008 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
@@ -22,4 +22,13 @@
protected JsLiteral(SourceInfo sourceInfo) {
super(sourceInfo);
}
+
+ /**
+ * Return true if the literal can be interned. A literal can be interned if it is constant, has
+ * value semantics and its construction does not involve evaluation of any construct other than
+ * internable literals.
+ */
+ public boolean isInternable() {
+ return false;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNumberLiteral.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNumberLiteral.java
index abf88e4..2d57e26 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNumberLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNumberLiteral.java
@@ -1,11 +1,11 @@
/*
* Copyright 2008 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
@@ -28,6 +28,14 @@
}
@Override
+ public boolean equals(Object that) {
+ if (that == null || this.getClass() != that.getClass()) {
+ return false;
+ }
+ return value == ((JsNumberLiteral) that).value;
+ }
+
+ @Override
public NodeKind getKind() {
return NodeKind.NUMBER;
}
@@ -37,6 +45,11 @@
}
@Override
+ public int hashCode() {
+ return new Double(value).hashCode();
+ }
+
+ @Override
public boolean isBooleanFalse() {
return value == 0.0;
}
@@ -61,4 +74,13 @@
v.visit(this, ctx);
v.endVisit(this, ctx);
}
+
+ /**
+ * Number literals are always internable but it is only profitable to intern numbers with more
+ * than 3 digits.
+ */
+ @Override
+ public boolean isInternable() {
+ return Double.toString(value).length() > 3;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java b/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java
index e7e12e6..e960081 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsObjectLiteral.java
@@ -1,11 +1,11 @@
/*
* Copyright 2008 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
@@ -23,24 +23,40 @@
*/
public final class JsObjectLiteral extends JsLiteral {
- private final List<JsPropertyInitializer> props = new ArrayList<JsPropertyInitializer>();
+ private final List<JsPropertyInitializer> properties = new ArrayList<JsPropertyInitializer>();
+
+ private boolean internable = false;
public JsObjectLiteral(SourceInfo sourceInfo) {
super(sourceInfo);
}
@Override
+ public boolean equals(Object that) {
+ if (that == null && this.getClass() != that.getClass()) {
+ return false;
+ }
+ JsObjectLiteral thatLiteral = (JsObjectLiteral) that;
+ return internable == thatLiteral.internable && properties.equals(thatLiteral.properties);
+ }
+
+ @Override
public NodeKind getKind() {
return NodeKind.OBJECT;
}
public List<JsPropertyInitializer> getPropertyInitializers() {
- return props;
+ return properties;
+ }
+
+ @Override
+ public int hashCode() {
+ return properties.hashCode() + 17 * (internable ? 0 : 1);
}
@Override
public boolean hasSideEffects() {
- for (JsPropertyInitializer prop : props) {
+ for (JsPropertyInitializer prop : properties) {
if (prop.hasSideEffects()) {
return true;
}
@@ -71,8 +87,21 @@
@Override
public void traverse(JsVisitor v, JsContext ctx) {
if (v.visit(this, ctx)) {
- v.acceptWithInsertRemove(props);
+ v.acceptWithInsertRemove(properties);
}
v.endVisit(this, ctx);
}
+
+ /**
+ * Some object literals are not mutated and hence internable.
+ */
+ @Override
+ public boolean isInternable() {
+ // There is no gain in interning the empty object.
+ return internable && properties.size() > 0;
+ }
+
+ public void setInternable() {
+ internable = true;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java b/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java
index ae67b25..51a14c7 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsPropertyInitializer.java
@@ -1,11 +1,11 @@
/*
* Copyright 2008 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
@@ -28,13 +28,26 @@
super(sourceInfo);
}
- public JsPropertyInitializer(SourceInfo sourceInfo, JsExpression labelExpr, JsExpression valueExpr) {
+ public JsPropertyInitializer(SourceInfo sourceInfo, JsExpression labelExpr,
+ JsExpression valueExpr) {
super(sourceInfo);
+
+ assert labelExpr instanceof JsStringLiteral || labelExpr instanceof JsNumberLiteral ||
+ labelExpr instanceof JsNameRef;
this.labelExpr = labelExpr;
this.valueExpr = valueExpr;
}
@Override
+ public boolean equals(Object that) {
+ if (that == null || that.getClass() != this.getClass()) {
+ return false;
+ }
+ return labelExpr.equals(((JsPropertyInitializer) that).labelExpr) &&
+ valueExpr.equals(((JsPropertyInitializer) that).valueExpr);
+ }
+
+ @Override
public NodeKind getKind() {
return NodeKind.PROPERTY_INIT;
}
@@ -47,11 +60,18 @@
return valueExpr;
}
+ @Override
+ public int hashCode() {
+ return labelExpr.hashCode() + 17 * valueExpr.hashCode();
+ }
+
public boolean hasSideEffects() {
return labelExpr.hasSideEffects() || valueExpr.hasSideEffects();
}
public void setLabelExpr(JsExpression labelExpr) {
+ assert labelExpr instanceof JsStringLiteral || labelExpr instanceof JsNumberLiteral ||
+ labelExpr instanceof JsNameRef : labelExpr.toString() + " is not a valid property label";
this.labelExpr = labelExpr;
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsRegExp.java b/dev/core/src/com/google/gwt/dev/js/ast/JsRegExp.java
index 76b2589..89d3fcf 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsRegExp.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsRegExp.java
@@ -1,11 +1,11 @@
/*
* Copyright 2008 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
@@ -14,6 +14,7 @@
package com.google.gwt.dev.js.ast;
import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.util.Util;
/**
* A JavaScript regular expression.
@@ -28,6 +29,16 @@
super(sourceInfo);
}
+ @Override
+ public boolean equals(Object that) {
+ // It is OK to check instance of here as JsRegExp is final.
+ if (!(that instanceof JsRegExp)) {
+ return false;
+ }
+ return Util.equalsNullCheck(flags,((JsRegExp) that).flags) &&
+ Util.equalsNullCheck(pattern,((JsRegExp) that).pattern);
+ }
+
public String getFlags() {
return flags;
}
@@ -42,6 +53,13 @@
}
@Override
+ public int hashCode() {
+ int flagsHashCode = (flags != null) ? flags.hashCode() : 0;
+ int patternHashCode = (pattern != null) ? pattern.hashCode() : 0;
+ return flagsHashCode + 17 * patternHashCode;
+ }
+
+ @Override
public boolean isBooleanFalse() {
return false;
}
@@ -61,6 +79,11 @@
return false;
}
+ @Override
+ public boolean isInternable() {
+ return true;
+ }
+
public void setFlags(String suffix) {
this.flags = suffix;
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsStringLiteral.java b/dev/core/src/com/google/gwt/dev/js/ast/JsStringLiteral.java
index d5738c7..05407f1 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsStringLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsStringLiteral.java
@@ -1,11 +1,11 @@
/*
* Copyright 2008 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
@@ -33,11 +33,25 @@
return NodeKind.STRING;
}
+ @Override
+ public boolean equals(Object that) {
+ // It is OK to check instance of here as JsStringLiteral is final.
+ if (!(that instanceof JsStringLiteral)) {
+ return false;
+ }
+ return value.equals(((JsStringLiteral) that).value);
+ }
+
public String getValue() {
return value;
}
@Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
public boolean isBooleanFalse() {
return value.length() == 0;
}
@@ -62,4 +76,10 @@
v.visit(this, ctx);
v.endVisit(this, ctx);
}
+
+ @Override
+ public boolean isInternable() {
+ // Do not intern empty strings.
+ return value.length() != 0;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 697ed16..a230bf7 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -146,7 +146,7 @@
public static String computeStrongName(byte[] content) {
return computeStrongName(new byte[][] {content});
}
-
+
/**
* Computes the MD5 hash of the specified byte arrays.
*
@@ -180,7 +180,7 @@
}
return StringUtils.toHexString(md5.digest());
}
-
+
public static void copy(InputStream is, OutputStream os) throws IOException {
try {
copyNoClose(is, os);
@@ -311,6 +311,16 @@
}
/**
+ * Equality check through equals() that is also satisfied if both objects are null.
+ */
+ public static boolean equalsNullCheck(Object thisObject, Object thatObject) {
+ if (thisObject == null) {
+ return thatObject == null;
+ }
+ return thisObject.equals(thatObject);
+ }
+
+ /**
* Escapes '&', '<', '>', '"', and '\'' to their XML entity equivalents.
*/
public static String escapeXml(String unescaped) {
@@ -497,7 +507,7 @@
/**
* A 4-digit hex result.
- *
+ *
* @deprecated use {@link StringUtils#hex4(char, StringBuffer)} instead.
*/
@Deprecated
@@ -989,11 +999,11 @@
/**
* Returns a string representation of the byte array as a series of
* hexadecimal characters.
- *
+ *
* @param bytes byte array to convert
* @return a string representation of the byte array as a series of
* hexadecimal characters
- * @deprecated use {@link StringUtils#toHexString(byte[])} instead.
+ * @deprecated use {@link StringUtils#toHexString(byte[])} instead.
*/
@Deprecated
public static String toHexString(byte[] bytes) {
diff --git a/dev/core/test/com/google/gwt/dev/js/JsLiteralInternerTest.java b/dev/core/test/com/google/gwt/dev/js/JsLiteralInternerTest.java
new file mode 100644
index 0000000..5f68d1d
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/js/JsLiteralInternerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2014 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;
+
+import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.js.ast.JsArrayLiteral;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsLiteral;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsObjectLiteral;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.TextOutput;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Verifies that {@link JsNamespaceChooser} can put globals into namespaces.
+ */
+public class JsLiteralInternerTest extends TestCase {
+
+ public void testSimpleIntern() throws Exception {
+ // Numbers
+ checkTranslation("var x = 22222, y = 22222;", "var $intern_0=22222;var x=$intern_0,y=$intern_0;");
+
+ // Objects
+ checkTranslation("var x = {a:12, b:14}, y = {a:12, b: 14}, z = {a:12, b:13};",
+ "var $intern_0={a:12,b:14};var x=$intern_0,y=$intern_0,z={a:12,b:13};");
+
+ checkTranslation("var x = {a:12, b:14}, y = {a:12, b: 14}, z = {a:12, b:13};",
+ "var $intern_0=12,$intern_1=14;var x={a:$intern_0,b:$intern_1}," +
+ "y={a:$intern_0,b:$intern_1},z={a:$intern_0,b:13};",false);
+
+ // Strings
+ checkTranslation("var x = 'abx', y = \"abx\" + 'abc', z = 'ab';",
+ "var $intern_0='abx';var x=$intern_0,y=$intern_0+'abc',z='ab';");
+
+ // Regexes
+ checkTranslation("var x = /abx/, y = /abx/ + /abc/, z = /ab/;",
+ "var $intern_0=/abx/;var x=$intern_0,y=$intern_0+/abc/,z=/ab/;");
+
+ // Arrays
+ checkTranslation("var x = [12,14], y = [12, 14], z = [12,13];",
+ "var $intern_0=[12,14];var x=$intern_0,y=$intern_0,z=[12,13];");
+
+ checkTranslation("var x = [12,14], y = [12, 14], z = [12, 13];",
+ "var $intern_0=12,$intern_1=14;var x=[$intern_0,$intern_1]," +
+ "y=[$intern_0,$intern_1],z=[$intern_0,13];",false);
+ }
+
+ public void testDoNotInternSmallNumbers() throws Exception {
+ checkTranslation("var x = 2, y = 2;", "var x=2,y=2;");
+ }
+
+ public void testDoNotInternSingleOccurrence() throws Exception {
+ checkTranslation("var x = 222222, y = 333333;", "var x=222222,y=333333;");
+ }
+
+ public void testDoNotInternEmptyObjectsOrArrayLiterals() throws Exception {
+ checkTranslation("var x = {}, y = {};", "var x={},y={};");
+ checkTranslation("var x = [], y = [];", "var x=[],y=[];");
+ }
+
+ public void testInternInLhs() throws Exception {
+ checkTranslation("var a = 'xxxx';a['xxxx']++;",
+ "var $intern_0='xxxx';var a=$intern_0;a[$intern_0]++;");
+ checkTranslation("var a = 'xxxx';++a['xxxx'];",
+ "var $intern_0='xxxx';var a=$intern_0;++a[$intern_0];");
+ }
+
+ public void testDoNotInternIllegalLhs() throws Exception {
+ checkTranslation("var a = 'xxxx';'xxxx'++;", "var a='xxxx';'xxxx'++;");
+ checkTranslation("var a = 'xxxx';++'xxxx';", "var a='xxxx';++'xxxx';");
+ // TODO(rluble): Should also check for literal on lhs of a binary op, but it seems that
+ // the JsParser already checks validity so it must be arrived at by a transformation.
+ }
+
+ private void checkTranslation(String source, String expectedJs)
+ throws IOException, JsParserException {
+ checkTranslation(source, expectedJs, true);
+ }
+
+ private void checkTranslation(String source, String expectedJs,
+ boolean internObjectAndArrayLiterals)
+ throws IOException, JsParserException {
+ JsProgram program = parseJs(source);
+ // Mark all object literals as internable
+ if (internObjectAndArrayLiterals) {
+ new JsModVisitor() {
+ @Override
+ public void endVisit(JsObjectLiteral x, JsContext ctx) {
+ x.setInternable();
+ }
+
+ @Override
+ public void endVisit(JsArrayLiteral x, JsContext ctx) {
+ x.setInternable();
+ }
+ }.accept(program);
+ }
+ exec(program);
+ String actual = serializeJs(program);
+ assertEquals(expectedJs, actual);
+ }
+
+ private Map<JsName, JsLiteral> exec(JsProgram program) {
+ // Prerequisite: resolve name references.
+ JsSymbolResolver.exec(program);
+ return JsLiteralInterner.exec(null, program, JsLiteralInterner.INTERN_ALL);
+ }
+
+ private static JsProgram parseJs(String js) throws IOException, JsParserException {
+ JsProgram program = new JsProgram();
+ List<JsStatement> statements = JsParser.parse(SourceOrigin.UNKNOWN, program.getScope(),
+ new StringReader(js));
+ program.getGlobalBlock().getStatements().addAll(statements);
+ return program;
+ }
+
+ private static String serializeJs(JsProgram program1) {
+ TextOutput text = new DefaultTextOutput(true);
+ JsVisitor generator = new JsSourceGenerationVisitor(text);
+ generator.accept(program1);
+ return text.toString();
+ }
+}