This patch allows constructs like "woot".replace('o', '0') to be statically evaluated and replaced directly with the result of the operation at compile time.  Also included is a peephole compile speed optimization in JProgram.

Suggested by: alex.tkachman
Patch by: me (based on patch by alex.tkachman)
Review by: mmendez
           alex.tkachman

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@447 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java
index 6232752..25e2995 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JBooleanLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java boolean literal expression.
  */
-public class JBooleanLiteral extends JLiteral {
+public class JBooleanLiteral extends JValueLiteral {
 
   private final boolean value;
 
@@ -38,6 +38,10 @@
     return value;
   }
 
+  public Object getValueObj() {
+    return Boolean.valueOf(value);
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JCharLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JCharLiteral.java
index 6df96b8..56d8de0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JCharLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JCharLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java character literal expression.
  */
-public class JCharLiteral extends JLiteral {
+public class JCharLiteral extends JValueLiteral {
 
   private final char value;
 
@@ -38,6 +38,10 @@
     return value;
   }
 
+  public Object getValueObj() {
+    return new Character(value);
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JDoubleLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JDoubleLiteral.java
index 6116d6a..e4bc15a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JDoubleLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JDoubleLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java double literal expression. 
  */
-public class JDoubleLiteral extends JLiteral {
+public class JDoubleLiteral extends JValueLiteral {
 
   private final double value;
 
@@ -38,6 +38,10 @@
     return value;
   }
 
+  public Object getValueObj() {
+    return new Double(value);
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JFloatLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JFloatLiteral.java
index 1ff769e..ea36a84 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JFloatLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JFloatLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java literal typed as a float.
  */
-public class JFloatLiteral extends JLiteral {
+public class JFloatLiteral extends JValueLiteral {
 
   private final float value;
 
@@ -38,6 +38,10 @@
     return value;
   }
 
+  public Object getValueObj() {
+    return new Float(value);
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JIntLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JIntLiteral.java
index 37832fd..14b1f76 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JIntLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JIntLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java integer literal expression.
  */
-public class JIntLiteral extends JLiteral {
+public class JIntLiteral extends JValueLiteral {
 
   private final int value;
 
@@ -38,6 +38,10 @@
     return value;
   }
 
+  public Object getValueObj() {
+    return new Integer(value);
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JLongLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JLongLiteral.java
index dc0ecee..e286d46e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JLongLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JLongLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java literal expression that evaluates to a Long.
  */
-public class JLongLiteral extends JLiteral {
+public class JLongLiteral extends JValueLiteral {
 
   private final long value;
 
@@ -38,6 +38,10 @@
     return value;
   }
 
+  public Object getValueObj() {
+    return new Long(value);
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java
index d775268..666598d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java null literal expression.
  */
-public class JNullLiteral extends JLiteral {
+public class JNullLiteral extends JValueLiteral {
 
   /**
    * These are only supposed to be constructed by JProgram.
@@ -31,6 +31,10 @@
     return program.getTypeNull();
   }
 
+  public Object getValueObj() {
+    return null;
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
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 a56a874..1ef2b37 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
@@ -30,7 +30,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 /**
  * Root for the AST representing an entire Java program.
@@ -150,6 +149,11 @@
                                                                        * JMethod>
                                                                        */();
 
+  private final Map/* <JMethod, JMethod> */staticToInstanceMap = new IdentityHashMap/*
+                                                                                     * <JMethod,
+                                                                                     * JMethod>
+                                                                                     */();
+
   private final JPrimitiveType typeBoolean = new JPrimitiveType(this,
       "boolean", "Z", literalFalse);
 
@@ -448,6 +452,11 @@
     return new JStringLiteral(this, String.valueOf(s));
   }
 
+  public JStringLiteral getLiteralString(String s) {
+    // should conslidate so we can build a string table in output code later?
+    return new JStringLiteral(this, s);
+  }
+
   public JField getNullField() {
     if (nullField == null) {
       nullField = new JField(this, null, "nullField", null, typeNull, false,
@@ -616,7 +625,7 @@
   }
 
   public boolean isStaticImpl(JMethod method) {
-    return instanceToStaticMap.containsValue(method);
+    return staticToInstanceMap.containsKey(method);
   }
 
   public void putIntoTypeMap(String qualifiedBinaryName, JReferenceType type) {
@@ -628,6 +637,7 @@
 
   public void putStaticImpl(JMethod method, JMethod staticImpl) {
     instanceToStaticMap.put(method, staticImpl);
+    staticToInstanceMap.put(staticImpl, method);
   }
 
   public JClassType rebind(JType type) {
@@ -662,13 +672,7 @@
    * returns <code>null</code>.
    */
   public JMethod staticImplFor(JMethod method) {
-    for (Iterator it = instanceToStaticMap.entrySet().iterator(); it.hasNext();) {
-      Map.Entry entry = (Entry) it.next();
-      if (entry.getValue() == method) {
-        return (JMethod) entry.getKey();
-      }
-    }
-    return null;
+    return (JMethod) staticToInstanceMap.get(method);
   }
 
   public JReferenceType strongerType(JReferenceType type1, JReferenceType type2) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java
index e3f7da3..694a1d0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java
@@ -18,7 +18,7 @@
 /**
  * Java literal expression that evaluates to a string.
  */
-public class JStringLiteral extends JLiteral {
+public class JStringLiteral extends JValueLiteral {
 
   private final String value;
 
@@ -38,6 +38,10 @@
     return value;
   }
 
+  public Object getValueObj() {
+    return value;
+  }
+
   public void traverse(JVisitor visitor, Context ctx) {
     if (visitor.visit(this, ctx)) {
     }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JValueLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JValueLiteral.java
new file mode 100644
index 0000000..2a267c3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JValueLiteral.java
@@ -0,0 +1,29 @@
+/*

+ * Copyright 2006 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.jjs.ast;

+

+/**

+ * Base class for any Java literal expression.

+ */

+public abstract class JValueLiteral extends JLiteral {

+

+  public JValueLiteral(JProgram program) {

+    super(program);

+  }

+

+  public abstract Object getValueObj();

+

+}

diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index b2c24a3..04d8fc8 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -21,25 +21,38 @@
 import com.google.gwt.dev.jjs.ast.JBlock;
 import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
 import com.google.gwt.dev.jjs.ast.JBreakStatement;
+import com.google.gwt.dev.jjs.ast.JCharLiteral;
 import com.google.gwt.dev.jjs.ast.JConditional;
 import com.google.gwt.dev.jjs.ast.JContinueStatement;
 import com.google.gwt.dev.jjs.ast.JDoStatement;
+import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JForStatement;
 import com.google.gwt.dev.jjs.ast.JIfStatement;
+import com.google.gwt.dev.jjs.ast.JIntLiteral;
 import com.google.gwt.dev.jjs.ast.JLocalRef;
+import com.google.gwt.dev.jjs.ast.JLongLiteral;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
 import com.google.gwt.dev.jjs.ast.JModVisitor;
 import com.google.gwt.dev.jjs.ast.JPrefixOperation;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
 import com.google.gwt.dev.jjs.ast.JStatement;
+import com.google.gwt.dev.jjs.ast.JStringLiteral;
 import com.google.gwt.dev.jjs.ast.JTryStatement;
+import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JUnaryOperator;
+import com.google.gwt.dev.jjs.ast.JValueLiteral;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.JWhileStatement;
 
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Attempts to remove dead code.
@@ -52,7 +65,7 @@
   public class DeadCodeVisitor extends JModVisitor {
 
     /**
-     * Short circuit boolean AND or OR expressions when possible.
+     * Short circuit binary operations.
      */
     public void endVisit(JBinaryOperation x, Context ctx) {
       JBinaryOperator op = x.getOp();
@@ -109,6 +122,15 @@
             && rhs.getType() == program.getTypeNull()) {
           ctx.replaceMe(program.getLiteralBoolean(false));
         }
+      } else if (op == JBinaryOperator.ADD
+          && x.getType() == program.getTypeJavaLangString()) {
+        // try to statically evaluate concatentation
+        if (lhs instanceof JValueLiteral && rhs instanceof JValueLiteral) {
+          Object lhsObj = ((JValueLiteral) lhs).getValueObj();
+          Object rhsObj = ((JValueLiteral) rhs).getValueObj();
+          ctx.replaceMe(program.getLiteralString(String.valueOf(lhsObj)
+              + String.valueOf(rhsObj)));
+        }
       }
     }
 
@@ -197,6 +219,16 @@
     }
 
     /**
+     * Resolve method calls that can be computed statically.
+     */
+    public void endVisit(JMethodCall x, Context ctx) {
+      JMethod method = x.getTarget();
+      if (method.getEnclosingType() == program.getTypeJavaLangString()) {
+        tryOptimizeStringCall(x, ctx, method);
+      }
+    }
+
+    /**
      * Resolve "!true" into "false" and vice versa.
      */
     public void endVisit(JPrefixOperation x, Context ctx) {
@@ -261,6 +293,10 @@
       }
     }
 
+    private Class mapType(JType type) {
+      return (Class) typeClassMap.get(type);
+    }
+
     private void removeMe(JStatement stmt, Context ctx) {
       if (ctx.canRemove()) {
         ctx.removeMe();
@@ -269,6 +305,104 @@
         ctx.replaceMe(new JBlock(program, stmt.getSourceInfo()));
       }
     }
+
+    /**
+     * Replace String methods having literal args with the static result.
+     */
+    private void tryOptimizeStringCall(JMethodCall x, Context ctx,
+        JMethod method) {
+
+      if (method.getType() == program.getTypeVoid()) {
+        return;
+      }
+
+      int skip = 0;
+      Object instance;
+      if (program.isStaticImpl(method)) {
+        // is it static implementation for instance method?
+        method = program.staticImplFor(method);
+        instance = tryTranslateLiteral((JExpression) x.getArgs().get(0),
+            String.class);
+        skip = 1;
+      } else {
+        // instance may be null
+        instance = tryTranslateLiteral(x.getInstance(), String.class);
+      }
+
+      if (instance == null && !method.isStatic()) {
+        return;
+      }
+
+      List params = method.getOriginalParamTypes();
+      Class paramTypes[] = new Class[params.size()];
+      Object paramValues[] = new Object[params.size()];
+      ArrayList args = x.getArgs();
+      for (int i = 0; i != params.size(); ++i) {
+        paramTypes[i] = mapType((JType) params.get(i));
+        if (paramTypes[i] == null) {
+          return;
+        }
+        paramValues[i] = tryTranslateLiteral((JExpression) args.get(i + skip),
+            paramTypes[i]);
+        if (paramValues[i] == null) {
+          return;
+        }
+      }
+
+      try {
+        Method actual = String.class.getMethod(method.getName(), paramTypes);
+        if (actual == null) {
+          return;
+        }
+        Object result = actual.invoke(instance, paramValues);
+        if (result instanceof String) {
+          ctx.replaceMe(program.getLiteralString((String) result));
+        } else if (result instanceof Boolean) {
+          ctx.replaceMe(program.getLiteralBoolean(((Boolean) result).booleanValue()));
+        } else if (result instanceof Character) {
+          ctx.replaceMe(program.getLiteralChar(((Character) result).charValue()));
+        } else if (result instanceof Integer) {
+          ctx.replaceMe(program.getLiteralInt(((Integer) result).intValue()));
+        } else {
+          boolean stopHere = true;
+        }
+      } catch (Exception e) {
+        // If the call threw an exception, just don't optimize
+        boolean stopHere = true;
+      }
+    }
+
+    private Object tryTranslateLiteral(JExpression maybeLit, Class type) {
+      if (!(maybeLit instanceof JValueLiteral)) {
+        return null;
+      }
+      // TODO: make this way better by a mile
+      if (type == boolean.class && maybeLit instanceof JBooleanLiteral) {
+        return Boolean.valueOf(((JBooleanLiteral) maybeLit).getValue());
+      }
+      if (type == char.class && maybeLit instanceof JCharLiteral) {
+        return new Character(((JCharLiteral) maybeLit).getValue());
+      }
+      if (type == double.class && maybeLit instanceof JDoubleLiteral) {
+        return new Double(((JDoubleLiteral) maybeLit).getValue());
+      }
+      if (type == float.class && maybeLit instanceof JIntLiteral) {
+        return new Float(((JIntLiteral) maybeLit).getValue());
+      }
+      if (type == int.class && maybeLit instanceof JIntLiteral) {
+        return new Integer(((JIntLiteral) maybeLit).getValue());
+      }
+      if (type == long.class && maybeLit instanceof JLongLiteral) {
+        return new Long(((JLongLiteral) maybeLit).getValue());
+      }
+      if (type == String.class && maybeLit instanceof JStringLiteral) {
+        return ((JStringLiteral) maybeLit).getValue();
+      }
+      if (type == Object.class && maybeLit instanceof JValueLiteral) {
+        return ((JValueLiteral) maybeLit).getValueObj();
+      }
+      return null;
+    }
   }
 
   /**
@@ -297,8 +431,20 @@
 
   private final JProgram program;
 
+  private final Map typeClassMap = new IdentityHashMap();
+
   public DeadCodeElimination(JProgram program) {
     this.program = program;
+    typeClassMap.put(program.getTypeJavaLangObject(), Object.class);
+    typeClassMap.put(program.getTypeJavaLangString(), String.class);
+    typeClassMap.put(program.getTypePrimitiveBoolean(), boolean.class);
+    typeClassMap.put(program.getTypePrimitiveByte(), byte.class);
+    typeClassMap.put(program.getTypePrimitiveChar(), char.class);
+    typeClassMap.put(program.getTypePrimitiveDouble(), double.class);
+    typeClassMap.put(program.getTypePrimitiveFloat(), float.class);
+    typeClassMap.put(program.getTypePrimitiveInt(), int.class);
+    typeClassMap.put(program.getTypePrimitiveLong(), long.class);
+    typeClassMap.put(program.getTypePrimitiveShort(), short.class);
   }
 
   private boolean execImpl() {
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
index 08a923b..479a315 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
@@ -60,6 +60,10 @@
 
   private static int sideEffectChecker;
 
+  private static String barShouldInline() {
+    return "bar";
+  }
+
   private static native boolean cannotOptimize() /*-{
     return true;
   }-*/;
@@ -479,6 +483,25 @@
     assertEquals(new Foo(1).i, 1);
     assertEquals(new Foo(2).i, 2);
   }
+  
+  public void testStringOptimizations() {
+    assertEquals("Herro, AJAX", "Hello, AJAX".replace('l', 'r'));
+    assertEquals('J', "Hello, AJAX".charAt(8));
+    assertEquals(11, "Hello, AJAX".length());
+    assertFalse("Hello, AJAX".equals("me"));
+    assertTrue("Hello, AJAX".equals("Hello, AJAX"));
+    assertTrue("Hello, AJAX".equalsIgnoreCase("HELLO, ajax"));
+    assertEquals("hello, ajax", "Hello, AJAX".toLowerCase());
+    
+    assertEquals("foobar", "foo" + barShouldInline());
+    assertEquals("1bar", 1 + barShouldInline());
+    assertEquals("fbar", 'f' + barShouldInline());
+    assertEquals("truebar", true + barShouldInline());
+    assertEquals("3.3bar", 3.3 + barShouldInline());
+    assertEquals("3.3bar", 3.3f + barShouldInline());
+    assertEquals("27bar", 27L + barShouldInline());
+    assertEquals("nullbar", null + barShouldInline());
+  }
 
   public void testSubclassStaticInnerAndClinitOrdering() {
     new CheckSubclassStaticInnerAndClinitOrdering();