In non-pretty mode, adds interning for castable-type-map literals. For any castmap occuring more than once, or occuring on an array type, it is hoisted to a top level variable, and it is referred to by name.

This should improve the performance of arrays and reduce uncompressed code size slightly, although it will increase the initial fragment size a little big without Closure Compiler usage. This is because all of the interned literals are added to the initial fragment, even those not used until a later fragment. With Closure, these will be moved near their point of use.

Review by: johnlenz@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10825 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
index 3e25125..4decab0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
@@ -37,7 +37,7 @@
 import com.google.gwt.dev.jjs.ast.js.JsCastMap;
 import com.google.gwt.dev.jjs.ast.js.JsCastMap.JsQueryType;
 import com.google.gwt.dev.jjs.ast.js.JsonArray;
-import com.google.gwt.dev.jjs.ast.js.JsonObject;
+import com.google.gwt.dev.util.collect.Lists;
 
 /**
  * Replace array accesses and instantiations with calls to the Array class.
@@ -126,7 +126,7 @@
     private JExpression getOrCreateCastMap(SourceInfo sourceInfo, JArrayType arrayType) {
       JsCastMap castableTypeMap = program.getCastMap(arrayType);
       if (castableTypeMap == null || castableTypeMap.getExprs().size() == 0) {
-        return new JsonObject(sourceInfo, program.getJavaScriptObject());
+        return new JsCastMap(sourceInfo, Lists.<JsQueryType>create(), program.getJavaScriptObject());
       }
       return castableTypeMap;
     }
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 b059bd8..7120ab7 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
@@ -151,7 +151,9 @@
 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.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;
@@ -189,6 +191,7 @@
 
     private final Stack<JsScope> scopeStack = new Stack<JsScope>();
 
+
     @Override
     public void endVisit(JArrayType x, Context ctx) {
       JsName name = topScope.declareName(x.getName());
@@ -202,6 +205,16 @@
     }
 
     @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);
@@ -552,6 +565,7 @@
      */
     private void setupSymbolicCastMaps() {
       namesByQueryId = new ArrayList<JsName>();
+
       for (JReferenceType type : program.getTypesByQueryId()) {
         String shortName;
         String longName;
@@ -591,6 +605,8 @@
 
     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;
@@ -1277,7 +1293,8 @@
       generateLongLiterals(vars);
       generateImmortalTypes(vars);
       generateQueryIdConstants(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?
@@ -1324,15 +1341,30 @@
       JsArrayLiteral arrayLit = (JsArrayLiteral) pop();
       SourceInfo sourceInfo = x.getSourceInfo();
       if (namesByQueryId == null || x.getExprs().size() == 0) {
-        // {2:1, 4:1, 12:1};
-        JsObjectLiteral objLit = new JsObjectLiteral(sourceInfo);
-        List<JsPropertyInitializer> props = objLit.getPropertyInitializers();
-        JsNumberLiteral one = new JsNumberLiteral(sourceInfo, 1);
-        for (JsExpression expr : arrayLit.getExpressions()) {
-          JsPropertyInitializer prop = new JsPropertyInitializer(sourceInfo, expr, one);
-          props.add(prop);
+        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(objLit);
       } else {
         // makeMap([Q_Object, Q_Foo, Q_Bar]);
         JsInvocation inv = new JsInvocation(sourceInfo);
@@ -1481,6 +1513,11 @@
         entryMethodToIndex.put(entryMethods.get(i), i);
       }
 
+      for (JDeclaredType type : x.getDeclaredTypes()) {
+        if (program.typeOracle.isInstantiatedType(type)) {
+          internCastMap(program.getCastMap(type));
+        }
+      }
       return true;
     }
 
@@ -1647,6 +1684,17 @@
       return false;
     }
 
+    private JsObjectLiteral castMapToObjectLiteral(JsArrayLiteral arrayLit, SourceInfo sourceInfo) {
+      JsObjectLiteral objLit = new JsObjectLiteral(sourceInfo);
+      List<JsPropertyInitializer> props = objLit.getPropertyInitializers();
+      JsNumberLiteral one = new JsNumberLiteral(sourceInfo, 1);
+      for (JsExpression expr : arrayLit.getExpressions()) {
+        JsPropertyInitializer prop = new JsPropertyInitializer(sourceInfo, expr, one);
+        props.add(prop);
+      }
+      return objLit;
+    }
+
     private void checkForDupMethods(JDeclaredType x) {
       // Sanity check to see that all methods are uniquely named.
       List<JMethod> methods = x.getMethods();
@@ -1896,6 +1944,16 @@
       }
     }
 
+    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();
@@ -1918,6 +1976,8 @@
       }
     }
 
+
+
     private void generateSeedFuncAndPrototype(JClassType x, List<JsStatement> globalStmts) {
       SourceInfo sourceInfo = x.getSourceInfo();
       if (x != program.getTypeJavaLangString()) {
@@ -2051,6 +2111,15 @@
       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;
@@ -2239,6 +2308,8 @@
     return generateJavaScriptAST.execImpl();
   }
 
+  private Map<String, JsExpression> castMapByString = new HashMap<String, JsExpression>();
+
   private final Map<JBlock, JsCatch> catchMap = new IdentityHashMap<JBlock, JsCatch>();
 
   private final Map<JClassType, JsScope> classScopes = new IdentityHashMap<JClassType, JsScope>();
@@ -2258,6 +2329,8 @@
    */
   private final JsScope interfaceScope;
 
+  private final Set<String> internedCastMap = new HashSet<String>();
+
   private final JsProgram jsProgram;
 
   private final Set<JConstructor> liveCtors = new IdentityHashSet<JConstructor>();
@@ -2274,6 +2347,7 @@
   private final Map<HasName, JsName> names = new IdentityHashMap<HasName, JsName>();
   private int nextSeedId = 1;
   private List<JsName> namesByQueryId;
+  private Map<String, JsName> namesByCastMap = new HashMap<String, JsName>();
   private JsFunction nullFunc;
 
   /**
@@ -2393,6 +2467,18 @@
     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;