Adds a string interning pass to the compile process.
One variable per unique string literal is added to a JsVars at the top of the program.
Patch by: bobv
Review by: scottb
Suggested by: bruce
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1475 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 524bcfd..ae8e990 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -53,6 +53,7 @@
import com.google.gwt.dev.js.JsObfuscateNamer;
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.JsVerboseNamer;
import com.google.gwt.dev.js.ast.JsProgram;
@@ -395,10 +396,13 @@
// (10) Obfuscate
if (obfuscate) {
+ JsStringInterner.exec(jsProgram);
JsObfuscateNamer.exec(jsProgram);
} else if (prettyNames) {
+ // We don't intern strings in pretty mode to improve readability
JsPrettyNamer.exec(jsProgram);
} else {
+ JsStringInterner.exec(jsProgram);
JsVerboseNamer.exec(jsProgram);
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java b/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java
new file mode 100644
index 0000000..ed077f5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsStringInterner.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2007 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.js.ast.JsBinaryOperation;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+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.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.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * 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.
+ */
+public class JsStringInterner {
+
+ public static final String PREFIX = "$intern_";
+
+ /**
+ * Replaces JsStringLiterals with JsNameRefs, creating new JsName allocations
+ * on the fly.
+ */
+ private static class StringVisitor extends JsModVisitor {
+ /**
+ * Records the scope in which the interned identifiers should be unique.
+ */
+ final JsScope scope;
+
+ /**
+ * This is a TreeMap to ensure consistent iteration order, based on the
+ * lexicographical ordering of the string constant.
+ */
+ final Map<JsStringLiteral, JsName> toCreate =
+ new TreeMap<JsStringLiteral, JsName>(new Comparator<JsStringLiteral>() {
+ public int compare(JsStringLiteral o1, JsStringLiteral o2) {
+ return o1.getValue().compareTo(o2.getValue());
+ }
+ });
+
+ /**
+ * 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.
+ */
+ long lastId = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param scope specifies the scope in which the interned strings should be
+ * created.
+ */
+ public StringVisitor(JsScope scope) {
+ this.scope = scope;
+ }
+
+ /**
+ * Replace JsStringLiteral instances with JsNameRefs.
+ */
+ @Override
+ public boolean visit(JsStringLiteral x, JsContext<JsExpression> ctx) {
+ JsName name = toCreate.get(x);
+ if (name == null) {
+ String ident = PREFIX + lastId++;
+ name = scope.declareName(ident);
+ toCreate.put(x, name);
+ }
+
+ ctx.replaceMe(name.makeRef());
+
+ 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<JsVar> ctx) {
+ return !(x.getName().getIdent().startsWith(PREFIX));
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
+ return !x.getOperator().isAssignment()
+ || !(x.getArg1() instanceof JsStringLiteral);
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsPostfixOperation x, JsContext<JsExpression> ctx) {
+ return !(x.getArg() instanceof JsStringLiteral);
+ }
+
+ /**
+ * Prevents 'fixing' an otherwise illegal operation.
+ */
+ @Override
+ public boolean visit(JsPrefixOperation x, JsContext<JsExpression> ctx) {
+ return !(x.getArg() instanceof JsStringLiteral);
+ }
+ }
+
+ public static boolean exec(JsProgram program) {
+ StringVisitor v = new StringVisitor(program.getScope());
+ v.accept(program);
+
+ if (v.toCreate.size() > 0) {
+ // Create the pool of variable names.
+ JsVars vars = new JsVars();
+ for (Map.Entry<JsStringLiteral, JsName> entry : v.toCreate.entrySet()) {
+ JsVar var = new JsVar(entry.getValue());
+ var.setInitExpr(entry.getKey());
+ vars.add(var);
+ }
+ program.getGlobalBlock().getStatements().add(0, vars);
+ }
+
+ return v.didChange();
+ }
+
+ /**
+ * Utility class.
+ */
+ private JsStringInterner() {
+ }
+}