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();