Don't allocate short names for unreferenced JsNames.
We create names during GenerateJavaScriptAST, but in many cases, references to those names get optimized out. However, the JsName still exists in the scope, and consumes a 'space'.
This change avoids wasting an allocation on unreferenced names, so that more obfuscated idents can fit into fewer characters.
http://gwt-code-reviews.appspot.com/1337803/show
Review by: cromwellian@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9675 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsNamer.java b/dev/core/src/com/google/gwt/dev/js/JsNamer.java
new file mode 100644
index 0000000..388f4fe
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsNamer.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsContext;
+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.JsName;
+import com.google.gwt.dev.js.ast.JsNameOf;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsParameter;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsScope;
+import com.google.gwt.dev.js.ast.JsVars;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A class that allocates unique identifiers for JsNames.
+ */
+public abstract class JsNamer {
+
+ private static Set<JsName> collectReferencedNames(JsProgram program) {
+ final Set<JsName> referenced = new HashSet<JsName>();
+ new JsVisitor() {
+ public void endVisit(JsForIn x, JsContext ctx) {
+ reference(x.getIterVarName());
+ }
+
+ public void endVisit(JsFunction x, JsContext ctx) {
+ reference(x.getName());
+ };
+
+ public void endVisit(JsLabel x, JsContext ctx) {
+ reference(x.getName());
+ };
+
+ public void endVisit(JsNameOf x, JsContext ctx) {
+ reference(x.getName());
+ };
+
+ public void endVisit(JsNameRef x, JsContext ctx) {
+ reference(x.getName());
+ };
+
+ public void endVisit(JsParameter x, JsContext ctx) {
+ reference(x.getName());
+ };
+
+ public void endVisit(JsVars.JsVar x, JsContext ctx) {
+ reference(x.getName());
+ };
+
+ private void reference(JsName name) {
+ if (name != null) {
+ referenced.add(name);
+ }
+ }
+ }.accept(program);
+ return referenced;
+ }
+
+ protected final JsProgram program;
+
+ protected final Set<JsName> referenced;
+
+ public JsNamer(JsProgram program) {
+ this.program = program;
+ referenced = collectReferencedNames(program);
+ }
+
+ protected final void execImpl() {
+ reset();
+ visit(program.getScope());
+ reset();
+ visit(program.getObjectScope());
+ }
+
+ protected abstract void reset();
+
+ protected abstract void visit(JsScope scope);
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsObfuscateNamer.java b/dev/core/src/com/google/gwt/dev/js/JsObfuscateNamer.java
index 90f125e..19c7820 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsObfuscateNamer.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsObfuscateNamer.java
@@ -17,17 +17,15 @@
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.js.ast.JsScope;
import java.util.Iterator;
-import java.util.List;
/**
* A namer that uses short, unrecognizable idents to minimize generated code
* size.
*/
-public class JsObfuscateNamer {
+public class JsObfuscateNamer extends JsNamer {
/**
* A lookup table of base-64 chars we use to encode idents.
@@ -48,19 +46,62 @@
*/
private int maxChildId = 0;
- private final JsProgram program;
-
/**
* A temp buffer big enough to hold at least 32 bits worth of base-64 chars.
*/
private final char[] sIdentBuf = new char[6];
public JsObfuscateNamer(JsProgram program) {
- this.program = program;
+ super(program);
}
- private void execImpl() {
- visit(program.getRootScope());
+ @Override
+ protected void reset() {
+ maxChildId = 0;
+ }
+
+ @Override
+ protected void visit(JsScope scope) {
+ // Save off the maxChildId which is currently being computed for my parent.
+ int mySiblingsMaxId = maxChildId;
+
+ /*
+ * Visit my children first. Reset maxChildId so that my children will get a
+ * clean slate: I do not communicate to my children.
+ */
+ maxChildId = 0;
+ for (JsScope child : scope.getChildren()) {
+ visit(child);
+ }
+ // maxChildId is now the max of all of my children's ids
+
+ // Visit my idents.
+ int curId = maxChildId;
+ for (Iterator<JsName> it = scope.getAllNames(); it.hasNext();) {
+ JsName name = it.next();
+ if (!referenced.contains(name)) {
+ // Don't allocate idents for non-referenced names.
+ continue;
+ }
+
+ if (!name.isObfuscatable()) {
+ // Unobfuscatable names become themselves.
+ name.setShortIdent(name.getIdent());
+ continue;
+ }
+
+ String newIdent;
+ while (true) {
+ // Get the next possible obfuscated name
+ newIdent = makeObfuscatedIdent(curId++);
+ if (isLegal(scope, newIdent)) {
+ break;
+ }
+ }
+ name.setShortIdent(newIdent);
+ }
+
+ maxChildId = Math.max(mySiblingsMaxId, curId);
}
private boolean isLegal(JsScope scope, String newIdent) {
@@ -94,48 +135,4 @@
return new String(sIdentBuf, 0, i);
}
-
- private void visit(JsScope scope) {
- // Save off the maxChildId which is currently being computed for my parent.
- int mySiblingsMaxId = maxChildId;
-
- /*
- * Visit my children first. Reset maxChildId so that my children will get a
- * clean slate: I do not communicate to my children.
- */
- maxChildId = 0;
- List<JsScope> children = scope.getChildren();
- for (Iterator<JsScope> it = children.iterator(); it.hasNext();) {
- visit(it.next());
- }
- // maxChildId is now the max of all of my children's ids
-
- JsRootScope rootScope = program.getRootScope();
- if (scope == rootScope) {
- return;
- }
-
- // Visit my idents.
- int curId = maxChildId;
- for (Iterator<JsName> it = scope.getAllNames(); it.hasNext();) {
- JsName name = it.next();
- if (!name.isObfuscatable()) {
- // Unobfuscatable names become themselves.
- name.setShortIdent(name.getIdent());
- continue;
- }
-
- String newIdent;
- while (true) {
- // Get the next possible obfuscated name
- newIdent = makeObfuscatedIdent(curId++);
- if (isLegal(scope, newIdent)) {
- break;
- }
- }
- name.setShortIdent(newIdent);
- }
-
- maxChildId = Math.max(mySiblingsMaxId, curId);
- }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsPrettyNamer.java b/dev/core/src/com/google/gwt/dev/js/JsPrettyNamer.java
index 4019943..6fe40f7 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsPrettyNamer.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsPrettyNamer.java
@@ -17,20 +17,17 @@
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.js.ast.JsScope;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.Iterator;
-import java.util.List;
import java.util.Set;
/**
* A namer that uses short, readable idents to maximize reability.
*/
-public class JsPrettyNamer {
+public class JsPrettyNamer extends JsNamer {
public static void exec(JsProgram program) {
new JsPrettyNamer(program).execImpl();
@@ -41,46 +38,17 @@
*/
private Set<String> childIdents = null;
- private final JsProgram program;
-
- /**
- * A map containing the next integer to try as an identifier suffix for a
- * given JsScope.
- */
- private IdentityHashMap<JsScope, HashMap<String, Integer>> startIdentForScope = new IdentityHashMap<JsScope, HashMap<String, Integer>>();
-
public JsPrettyNamer(JsProgram program) {
- this.program = program;
+ super(program);
}
- private void execImpl() {
- visit(program.getRootScope());
+ @Override
+ protected void reset() {
+ childIdents = new HashSet<String>();
}
- private boolean isLegal(JsScope scope, Set<String> childIdents,
- String newIdent) {
- if (JsKeywords.isKeyword(newIdent)) {
- return false;
- }
- if (childIdents.contains(newIdent)) {
- // one of my children already claimed this ident
- return false;
- }
- /*
- * Never obfuscate a name into an identifier that conflicts with an existing
- * unobfuscatable name! It's okay if it conflicts with an existing
- * obfuscatable name; that name will get obfuscated out of the way.
- */
- return (scope.findExistingUnobfuscatableName(newIdent) == null);
- }
-
- private void visit(JsScope scope) {
- HashMap<String, Integer> startIdent = startIdentForScope.get(scope);
- if (startIdent == null) {
- startIdent = new HashMap<String, Integer>();
- startIdentForScope.put(scope, startIdent);
- }
-
+ @Override
+ protected void visit(JsScope scope) {
// Save off the childIdents which is currently being computed for my parent.
Set<String> myChildIdents = childIdents;
@@ -89,19 +57,22 @@
* clean slate: I do not communicate to my children.
*/
childIdents = new HashSet<String>();
- List<JsScope> children = scope.getChildren();
- for (Iterator<JsScope> it = children.iterator(); it.hasNext();) {
- visit(it.next());
+ for (JsScope child : scope.getChildren()) {
+ visit(child);
}
+ // Child idents now contains all idents my children are using.
- JsRootScope rootScope = program.getRootScope();
- if (scope == rootScope) {
- return;
- }
+ // The next integer to try as an identifier suffix.
+ HashMap<String, Integer> startIdent = new HashMap<String, Integer>();
// Visit all my idents.
for (Iterator<JsName> it = scope.getAllNames(); it.hasNext();) {
JsName name = it.next();
+ if (!referenced.contains(name)) {
+ // Don't allocate idents for non-referenced names.
+ continue;
+ }
+
if (!name.isObfuscatable()) {
// Unobfuscatable names become themselves.
name.setShortIdent(name.getIdent());
@@ -130,4 +101,21 @@
myChildIdents.addAll(childIdents);
childIdents = myChildIdents;
}
+
+ private boolean isLegal(JsScope scope, Set<String> childIdents,
+ String newIdent) {
+ if (JsKeywords.isKeyword(newIdent)) {
+ return false;
+ }
+ if (childIdents.contains(newIdent)) {
+ // one of my children already claimed this ident
+ return false;
+ }
+ /*
+ * Never obfuscate a name into an identifier that conflicts with an existing
+ * unobfuscatable name! It's okay if it conflicts with an existing
+ * obfuscatable name; that name will get obfuscated out of the way.
+ */
+ return (scope.findExistingUnobfuscatableName(newIdent) == null);
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsVerboseNamer.java b/dev/core/src/com/google/gwt/dev/js/JsVerboseNamer.java
index 869e221..73c3552 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsVerboseNamer.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsVerboseNamer.java
@@ -17,52 +17,44 @@
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.js.ast.JsScope;
import java.util.Iterator;
-import java.util.List;
/**
* A namer that uses long, fully qualified names for maximum unambiguous
* debuggability.
*/
-public class JsVerboseNamer {
+public class JsVerboseNamer extends JsNamer {
public static void exec(JsProgram program) {
new JsVerboseNamer(program).execImpl();
}
- private final JsProgram program;
-
public JsVerboseNamer(JsProgram program) {
- this.program = program;
+ super(program);
}
- private void execImpl() {
- visit(program.getRootScope());
+ @Override
+ protected void reset() {
+ // Nothing to do.
}
- private boolean isLegal(String newIdent) {
- // only keywords are forbidden
- return !JsKeywords.isKeyword(newIdent);
- }
-
- private void visit(JsScope scope) {
+ @Override
+ protected void visit(JsScope scope) {
// Visit children.
- List<JsScope> children = scope.getChildren();
- for (Iterator<JsScope> it = children.iterator(); it.hasNext();) {
- visit(it.next());
- }
-
- JsRootScope rootScope = program.getRootScope();
- if (scope == rootScope) {
- return;
+ for (JsScope child : scope.getChildren()) {
+ visit(child);
}
// Visit all my idents.
for (Iterator<JsName> it = scope.getAllNames(); it.hasNext();) {
JsName name = it.next();
+ if (!referenced.contains(name)) {
+ // Don't allocate idents for non-referenced names.
+ continue;
+ }
+
if (!name.isObfuscatable()) {
// Unobfuscatable names become themselves.
name.setShortIdent(name.getIdent());
@@ -85,4 +77,9 @@
}
}
}
+
+ private boolean isLegal(String newIdent) {
+ // only keywords are forbidden
+ return !JsKeywords.isKeyword(newIdent);
+ }
}