Lots of static evaluation rules, mostly on the Java trees.
JsStaticEval: changes !!x to x within any boolean context
GenerateJavaAST: change if (x == null) to if (!x), if (x != null) to if (!!x) (except where x can be String)
DeadCodeElimination:
- constant fold all operations on all types of literals
- != and == where on argument is true or false
- +/- 0
- * or / on 1, -1
- x*0 -> 0
- 0-x -> -x
- if (!x) foo; else bar; -> if (x) bar; else foo;
- (!x) ? foo : bar; -> x ? bar : foo
- ^ with a literal true or false
- "- -x" -> "x"
- <</>>/>>> with an argument of 0
Issue: 1511
Patch by: spoon (w/ minor mods me JsStaticEval)
Review by: me
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2515 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 c5cfc9a..1f1b019 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
@@ -55,6 +55,7 @@
import com.google.gwt.dev.jjs.ast.JSwitchStatement;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JUnaryOperation;
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.JValueLiteral;
import com.google.gwt.dev.jjs.ast.JVariableRef;
@@ -77,10 +78,15 @@
public class DeadCodeElimination {
/**
- * Eliminates dead or unreachable code when possible.
+ * Eliminates dead or unreachable code when possible, and makes local
+ * simplifications like changing "<code>x || true</code>" to "<code>x</code>".
*
* TODO: leverage ignoring expression output more to remove intermediary
* operations in favor of pure side effects.
+ *
+ * TODO(spoon): move more simplifications into methods like
+ * {@link #simplifyCast(JExpression, JType, JExpression) simplifyCast}, so
+ * that more simplifications can be made on a single pass through a tree.
*/
public class DeadCodeVisitor extends JModVisitor {
@@ -116,6 +122,11 @@
JBinaryOperator op = x.getOp();
JExpression lhs = x.getLhs();
JExpression rhs = x.getRhs();
+ if ((lhs instanceof JValueLiteral) && (rhs instanceof JValueLiteral)) {
+ if (evalOpOnLiterals(op, (JValueLiteral) lhs, (JValueLiteral) rhs, ctx)) {
+ return;
+ }
+ }
switch (op) {
case AND:
shortCircuitAnd(lhs, rhs, ctx);
@@ -123,23 +134,48 @@
case OR:
shortCircuitOr(lhs, rhs, ctx);
break;
+ case BIT_XOR:
+ simplifyXor(lhs, rhs, ctx);
+ break;
case EQ:
// simplify: null == null -> true
if (lhs.getType() == program.getTypeNull()
&& rhs.getType() == program.getTypeNull() && !x.hasSideEffects()) {
ctx.replaceMe(program.getLiteralBoolean(true));
+ return;
}
+ simplifyEq(lhs, rhs, ctx, false);
break;
case NEQ:
// simplify: null != null -> false
if (lhs.getType() == program.getTypeNull()
&& rhs.getType() == program.getTypeNull() && !x.hasSideEffects()) {
ctx.replaceMe(program.getLiteralBoolean(false));
+ return;
}
+ simplifyEq(lhs, rhs, ctx, true);
break;
case ADD:
if (x.getType() == program.getTypeJavaLangString()) {
evalConcat(lhs, rhs, ctx);
+ break;
+ }
+ simplifyAdd(lhs, rhs, ctx, x.getType());
+ break;
+ case SUB:
+ simplifySub(lhs, rhs, ctx, x.getType());
+ break;
+ case MUL:
+ simplifyMul(lhs, rhs, ctx, x.getType());
+ break;
+ case DIV:
+ simplifyDiv(lhs, rhs, ctx, x.getType());
+ break;
+ case SHL:
+ case SHR:
+ case SHRU:
+ if (isLiteralZero(rhs)) {
+ ctx.replaceMe(lhs);
}
break;
default:
@@ -199,15 +235,9 @@
@Override
public void endVisit(JCastOperation x, Context ctx) {
- // Statically evaluate casting literals.
- if (x.getCastType() instanceof JPrimitiveType
- && x.getExpr() instanceof JValueLiteral) {
- JPrimitiveType type = (JPrimitiveType) x.getCastType();
- JValueLiteral lit = (JValueLiteral) x.getExpr();
- lit = type.coerceLiteral(lit);
- if (lit != null) {
- ctx.replaceMe(lit);
- }
+ JExpression updated = simplifyCast(x, x.getCastType(), x.getExpr());
+ if (updated != x) {
+ ctx.replaceMe(updated);
}
}
@@ -261,6 +291,14 @@
thenExpr);
ctx.replaceMe(binOp);
}
+ } else {
+ // e.g. (!cond ? then : else) -> (cond ? else : then)
+ JExpression unflipped = maybeUnflipBoolean(condExpr);
+ if (unflipped != null) {
+ ctx.replaceMe(new JConditional(program, x.getSourceInfo(),
+ x.getType(), unflipped, elseExpr, thenExpr));
+ return;
+ }
}
}
@@ -370,8 +408,22 @@
// just prune me
removeMe(x, ctx);
}
- } else if (isEmpty(thenStmt) && isEmpty(elseStmt)) {
+ return;
+ }
+
+ if (isEmpty(thenStmt) && isEmpty(elseStmt)) {
ctx.replaceMe(expr.makeStatement());
+ return;
+ }
+
+ if (!isEmpty(elseStmt)) {
+ // if (!cond) foo else bar -> if (cond) bar else foo
+ JExpression unflipped = maybeUnflipBoolean(expr);
+ if (unflipped != null) {
+ ctx.replaceMe(new JIfStatement(program, x.getSourceInfo(), unflipped,
+ elseStmt, thenStmt));
+ return;
+ }
}
}
@@ -488,13 +540,14 @@
if (x.getOp().isModifying()) {
lvalues.remove(x.getArg());
}
+ if (x.getArg() instanceof JValueLiteral) {
+ if (evalOpOnLiteral(x.getOp(), (JValueLiteral) x.getArg(), ctx)) {
+ return;
+ }
+ }
if (x.getOp() == JUnaryOperator.NOT) {
JExpression arg = x.getArg();
- if (arg instanceof JBooleanLiteral) {
- // e.g. !true -> false; !false -> true
- JBooleanLiteral booleanLiteral = (JBooleanLiteral) arg;
- ctx.replaceMe(program.getLiteralBoolean(!booleanLiteral.getValue()));
- } else if (arg instanceof JBinaryOperation) {
+ if (arg instanceof JBinaryOperation) {
// try to invert the binary operator
JBinaryOperation argOp = (JBinaryOperation) arg;
JBinaryOperator op = argOp.getOp();
@@ -533,12 +586,18 @@
ctx.replaceMe(argOp.getArg());
}
}
+ } else if (x.getOp() == JUnaryOperator.NEG) {
+ JExpression updated = simplifyNegate(x, x.getArg());
+ if (updated != x) {
+ ctx.replaceMe(updated);
+ }
}
}
/**
* Optimize switch statements.
*/
+ @Override
public void endVisit(JSwitchStatement x, Context ctx) {
switchBlocks.remove(x.getBody());
@@ -707,6 +766,329 @@
}
}
+ /**
+ * Evaluate <code>lhs == rhs</code>.
+ *
+ * @param lhs Any literal other than null.
+ * @param rhs Any literal other than null.
+ * @return Whether <code>lhs == rhs</code> will evaluate to
+ * <code>true</code> at run time.
+ */
+ private boolean evalEq(JValueLiteral lhs, JValueLiteral rhs) {
+ if (isTypeBoolean(lhs)) {
+ return toBoolean(lhs) == toBoolean(rhs);
+ }
+ if (isTypeDouble(lhs) || isTypeDouble(rhs)) {
+ return toDouble(lhs) == toDouble(rhs);
+ }
+ if (isTypeFloat(lhs) || isTypeFloat(rhs)) {
+ return toFloat(lhs) == toFloat(rhs);
+ }
+ if (isTypeLong(lhs) || isTypeLong(rhs)) {
+ return toLong(lhs) == toLong(rhs);
+ }
+ return toInt(lhs) == toInt(rhs);
+ }
+
+ /**
+ * Static evaluation of a unary operation on a literal.
+ *
+ * @return Whether a change was made
+ */
+ private boolean evalOpOnLiteral(JUnaryOperator op, JValueLiteral exp,
+ Context ctx) {
+ switch (op) {
+ case BIT_NOT: {
+ long value = toLong(exp);
+ long res = ~value;
+ if (isTypeLong(exp)) {
+ ctx.replaceMe(program.getLiteralLong(res));
+ } else {
+ ctx.replaceMe(program.getLiteralInt((int) res));
+ }
+ return true;
+ }
+
+ case NEG:
+ if (isTypeLong(exp)) {
+ ctx.replaceMe(program.getLiteralLong(-toLong(exp)));
+ return true;
+ }
+ if (isTypeIntegral(exp)) {
+ ctx.replaceMe(program.getLiteralInt(-toInt(exp)));
+ return true;
+ }
+ if (isTypeDouble(exp)) {
+ ctx.replaceMe(program.getLiteralDouble(-toDouble(exp)));
+ return true;
+ }
+ if (isTypeFloat(exp)) {
+ ctx.replaceMe(program.getLiteralFloat(-toFloat(exp)));
+ return true;
+ }
+ return false;
+
+ case NOT: {
+ JBooleanLiteral booleanLit = (JBooleanLiteral) exp;
+ ctx.replaceMe(program.getLiteralBoolean(!booleanLit.getValue()));
+ return true;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Static evaluation of a binary operation on two literals.
+ *
+ * @return Whether a change was made
+ */
+ private boolean evalOpOnLiterals(JBinaryOperator op, JValueLiteral lhs,
+ JValueLiteral rhs, Context ctx) {
+ if (isTypeString(lhs) || isTypeString(rhs) || isTypeNull(lhs)
+ || isTypeNull(rhs)) {
+ // String simplifications are handled elsewhere.
+ // Null can only be used with String append, and with
+ // comparison with EQ and NEQ, and those simplifications
+ // are also handled elsewhere.
+ return false;
+ }
+ switch (op) {
+ case EQ: {
+ ctx.replaceMe(program.getLiteralBoolean(evalEq(lhs, rhs)));
+ return true;
+ }
+
+ case NEQ: {
+ ctx.replaceMe(program.getLiteralBoolean(!evalEq(lhs, rhs)));
+ return true;
+ }
+
+ case ADD:
+ case SUB:
+ case MUL:
+ case DIV:
+ case MOD: {
+ if (isTypeDouble(lhs) || isTypeFloat(rhs) || isTypeDouble(lhs)
+ || isTypeFloat(rhs)) {
+ // do the op on doubles and cast back
+ double left = toDouble(lhs);
+ double right = toDouble(rhs);
+ double res;
+ switch (op) {
+ case ADD:
+ res = left + right;
+ break;
+ case SUB:
+ res = left - right;
+ break;
+ case MUL:
+ res = left * right;
+ break;
+ case DIV:
+ res = left / right;
+ break;
+ case MOD:
+ res = left % right;
+ break;
+ default:
+ assert false;
+ return false;
+ }
+ if (isTypeDouble(lhs) || isTypeDouble(rhs)) {
+ ctx.replaceMe(program.getLiteralDouble(res));
+ } else {
+ ctx.replaceMe(program.getLiteralFloat((float) res));
+ }
+ return true;
+ } else {
+ // do the op on longs and cast to the correct
+ // result type at the end
+ long left = toLong(lhs);
+ long right = toLong(rhs);
+
+ long res;
+ switch (op) {
+ case ADD:
+ res = left + right;
+ break;
+ case SUB:
+ res = left - right;
+ break;
+ case MUL:
+ res = left * right;
+ break;
+ case DIV:
+ res = left / right;
+ break;
+ case MOD:
+ res = left % right;
+ break;
+ default:
+ assert false;
+ return false;
+ }
+ if (isTypeLong(lhs) || isTypeLong(rhs)) {
+ ctx.replaceMe(program.getLiteralLong(res));
+ } else {
+ ctx.replaceMe(program.getLiteralInt((int) res));
+ }
+ return true;
+ }
+ }
+
+ case LT:
+ case LTE:
+ case GT:
+ case GTE: {
+ if (isTypeDouble(lhs) || isTypeDouble(rhs) || isTypeFloat(lhs)
+ || isTypeFloat(rhs)) {
+ // operate on doubles
+ double left = toDouble(lhs);
+ double right = toDouble(rhs);
+ boolean res;
+ switch (op) {
+ case LT:
+ res = left < right;
+ break;
+ case LTE:
+ res = left <= right;
+ break;
+ case GT:
+ res = left > right;
+ break;
+ case GTE:
+ res = left >= right;
+ break;
+ default:
+ assert false;
+ return false;
+ }
+ ctx.replaceMe(program.getLiteralBoolean(res));
+ return true;
+ } else {
+ // operate on longs
+ long left = toLong(lhs);
+ long right = toLong(rhs);
+ boolean res;
+ switch (op) {
+ case LT:
+ res = left < right;
+ break;
+ case LTE:
+ res = left <= right;
+ break;
+ case GT:
+ res = left > right;
+ break;
+ case GTE:
+ res = left >= right;
+ break;
+ default:
+ assert false;
+ return false;
+ }
+ ctx.replaceMe(program.getLiteralBoolean(res));
+ return true;
+ }
+ }
+
+ case BIT_AND:
+ case BIT_OR:
+ case BIT_XOR:
+ if (isTypeBoolean(lhs)) {
+ // TODO: maybe eval non-short-circuit boolean operators.
+ return false;
+ } else {
+ // operate on longs and then cast down
+ long left = toLong(lhs);
+ long right = toLong(rhs);
+ long res;
+ switch (op) {
+ case BIT_AND:
+ res = left & right;
+ break;
+
+ case BIT_OR:
+ res = left | right;
+ break;
+
+ case BIT_XOR:
+ res = left ^ right;
+ break;
+
+ default:
+ assert false;
+ return false;
+ }
+ if (isTypeLong(lhs) || isTypeLong(rhs)) {
+ ctx.replaceMe(program.getLiteralLong(res));
+ } else {
+ ctx.replaceMe(program.getLiteralInt((int) res));
+ }
+ return true;
+ }
+
+ case SHL:
+ case SHR:
+ case SHRU: {
+ if (isTypeLong(lhs)) {
+ long left = toLong(lhs);
+ int right = toInt(rhs);
+ long res;
+ switch (op) {
+ case SHL:
+ res = left << right;
+ break;
+
+ case SHR:
+ res = left >> right;
+ break;
+
+ case SHRU:
+ res = left >>> right;
+ break;
+
+ default:
+ assert false;
+ return false;
+ }
+
+ ctx.replaceMe(program.getLiteralLong(res));
+ return true;
+ } else {
+ int left = toInt(lhs);
+ int right = toInt(rhs);
+ int res;
+ switch (op) {
+ case SHL:
+ res = left << right;
+ break;
+
+ case SHR:
+ res = left >> right;
+ break;
+
+ case SHRU:
+ res = left >>> right;
+ break;
+
+ default:
+ assert false;
+ return false;
+ }
+
+ ctx.replaceMe(program.getLiteralInt(res));
+ return true;
+ }
+ }
+
+ default:
+ return false;
+ }
+ }
+
private boolean hasNoDefaultCase(JSwitchStatement x) {
JBlock body = x.getBody();
boolean inDefault = false;
@@ -740,6 +1122,123 @@
return (stmt instanceof JBlock && ((JBlock) stmt).statements.isEmpty());
}
+ private boolean isLiteralNegativeOne(JExpression exp) {
+ if (exp instanceof JValueLiteral) {
+ JValueLiteral lit = (JValueLiteral) exp;
+ if (isTypeIntegral(lit)) {
+ if (toLong(lit) == -1) {
+ return true;
+ }
+ }
+ if (isTypeFloatOrDouble(lit)) {
+ if (toDouble(lit) == -1.0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isLiteralOne(JExpression exp) {
+ if (exp instanceof JValueLiteral) {
+ JValueLiteral lit = (JValueLiteral) exp;
+ if (isTypeIntegral(lit)) {
+ if (toLong(lit) == 1) {
+ return true;
+ }
+ }
+ if (isTypeFloatOrDouble(lit)) {
+ if (toDouble(lit) == 1.0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isLiteralZero(JExpression exp) {
+ if (exp instanceof JValueLiteral) {
+ JValueLiteral lit = (JValueLiteral) exp;
+ if (toDouble(lit) == 0.0) {
+ // Using toDouble only is safe even for integer types. All types but
+ // long will keep full precision. Longs will lose precision, but
+ // it will not affect whether the resulting double is zero or not.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isTypeBoolean(JExpression lhs) {
+ return lhs.getType() == program.getTypePrimitiveBoolean();
+ }
+
+ private boolean isTypeDouble(JExpression exp) {
+ return isTypeDouble(exp.getType());
+ }
+
+ private boolean isTypeDouble(JType type) {
+ return type == program.getTypePrimitiveDouble();
+ }
+
+ private boolean isTypeFloat(JExpression exp) {
+ return isTypeFloat(exp.getType());
+ }
+
+ private boolean isTypeFloat(JType type) {
+ return type == program.getTypePrimitiveFloat();
+ }
+
+ /**
+ * Return whether the type of the expression is float or double.
+ */
+ private boolean isTypeFloatOrDouble(JExpression exp) {
+ return isTypeFloatOrDouble(exp.getType());
+ }
+
+ private boolean isTypeFloatOrDouble(JType type) {
+ return ((type == program.getTypePrimitiveDouble()) || (type == program.getTypePrimitiveFloat()));
+ }
+
+ /**
+ * Return whether the type of the expression is byte, char, short, int, or
+ * long.
+ */
+ private boolean isTypeIntegral(JExpression exp) {
+ return isTypeIntegral(exp.getType());
+ }
+
+ private boolean isTypeIntegral(JType type) {
+ return ((type == program.getTypePrimitiveInt())
+ || (type == program.getTypePrimitiveLong())
+ || (type == program.getTypePrimitiveChar())
+ || (type == program.getTypePrimitiveByte()) || (type == program.getTypePrimitiveShort()));
+ }
+
+ private boolean isTypeLong(JExpression exp) {
+ return isTypeLong(exp.getType());
+ }
+
+ private boolean isTypeLong(JType type) {
+ return type == program.getTypePrimitiveLong();
+ }
+
+ private boolean isTypeNull(JExpression exp) {
+ return isTypeNull(exp.getType());
+ }
+
+ private boolean isTypeNull(JType type) {
+ return type == program.getTypeNull();
+ }
+
+ private boolean isTypeString(JExpression exp) {
+ return isTypeString(exp.getType());
+ }
+
+ private boolean isTypeString(JType type) {
+ return type == program.getTypeJavaLangString();
+ }
+
private boolean isUnconditionalBreak(JStatement statement) {
if (statement instanceof JBreakStatement) {
return true;
@@ -772,6 +1271,20 @@
return call;
}
+ /**
+ * Negate the supplied expression if negating it makes the expression
+ * shorter. Otherwise, return null.
+ */
+ private JExpression maybeUnflipBoolean(JExpression expr) {
+ if (expr instanceof JUnaryOperation) {
+ JUnaryOperation unop = (JUnaryOperation) expr;
+ if (unop.getOp() == JUnaryOperator.NOT) {
+ return unop.getArg();
+ }
+ }
+ return null;
+ }
+
private int numRemovableExpressions(JMultiExpression x) {
if (ignoringExpressionOutput.contains(x)) {
// The result doesn't matter: all expressions can be removed.
@@ -908,6 +1421,274 @@
}
}
+ private boolean simplifyAdd(JExpression lhs, JExpression rhs, Context ctx,
+ JType type) {
+ if (isLiteralZero(rhs)) {
+ ctx.replaceMe(simplifyCast(type, lhs));
+ return true;
+ }
+ if (isLiteralZero(lhs)) {
+ ctx.replaceMe(simplifyCast(type, rhs));
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Simplify <code>exp == bool</code>, where <code>bool</code> is a
+ * boolean literal.
+ */
+ private void simplifyBooleanEq(JExpression exp, boolean bool, Context ctx) {
+ if (bool) {
+ ctx.replaceMe(exp);
+ } else {
+ ctx.replaceMe(new JPrefixOperation(program, exp.getSourceInfo(),
+ JUnaryOperator.NOT, exp));
+ }
+ }
+
+ /**
+ * Simplify <code>lhs == rhs</code>, where <code>lhs</code> and
+ * <code>rhs</code> are known to be boolean. If <code>negate</code> is
+ * <code>true</code>, then treat it as <code>lhs != rhs</code> instead
+ * of <code>lhs == rhs</code>. Assumes that the case where both sides are
+ * literals has already been checked.
+ */
+ private void simplifyBooleanEq(JExpression lhs, JExpression rhs,
+ Context ctx, boolean negate) {
+ if (lhs instanceof JBooleanLiteral) {
+ boolean left = ((JBooleanLiteral) lhs).getValue();
+ simplifyBooleanEq(rhs, left ^ negate, ctx);
+ return;
+ }
+ if (rhs instanceof JBooleanLiteral) {
+ boolean right = ((JBooleanLiteral) rhs).getValue();
+ simplifyBooleanEq(lhs, right ^ negate, ctx);
+ return;
+ }
+ }
+
+ /**
+ * Simplify a cast operation. Return <code>original</code> if it is
+ * equivalent to the desired return value.
+ *
+ * TODO: Simplify casts of casts, e.g. (int)(long)foo.
+ *
+ * @param original Either <code>null</code>, or a cast from
+ * <code>exp</code> to <code>type</code>
+ * @param type The type to cast to
+ * @param exp The expression being cast
+ * @return An expression equivalent to a cast from <code>exp</code> to
+ * <code>type</code>, but possibly simplified
+ */
+ private JExpression simplifyCast(JExpression original, JType type,
+ JExpression exp) {
+ if (type == exp.getType()) {
+ return exp;
+ }
+ if ((type instanceof JPrimitiveType) && (exp instanceof JValueLiteral)) {
+ // Statically evaluate casting literals.
+ JPrimitiveType typePrim = (JPrimitiveType) type;
+ JValueLiteral expLit = (JValueLiteral) exp;
+ JValueLiteral casted = typePrim.coerceLiteral(expLit);
+ if (casted != null) {
+ return casted;
+ }
+ }
+
+ if (type == program.getTypePrimitiveInt()) {
+ JType expType = exp.getType();
+ if ((expType == program.getTypePrimitiveShort())
+ || (expType == program.getTypePrimitiveChar())
+ || (expType == program.getTypePrimitiveByte())) {
+ // Discard casts from char, byte, or short to int, because
+ // such casts re always implicit anyway.
+ return exp;
+ }
+ }
+
+ // no simplification made
+ if (original != null) {
+ return original;
+ }
+ return new JCastOperation(program, exp.getSourceInfo(), type, exp);
+ }
+
+ private JExpression simplifyCast(JType type, JExpression exp) {
+ return simplifyCast(null, type, exp);
+ }
+
+ private boolean simplifyDiv(JExpression lhs, JExpression rhs, Context ctx,
+ JType type) {
+ if (isLiteralOne(rhs)) {
+ ctx.replaceMe(simplifyCast(type, lhs));
+ return true;
+ }
+ if (isLiteralNegativeOne(rhs)) {
+ ctx.replaceMe(simplifyNegate(lhs));
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Simplify <code>lhs == rhs</code>. If <code>negate</code> is true,
+ * then it's actually static evaluation of <code>lhs != rhs</code>.
+ */
+ private void simplifyEq(JExpression lhs, JExpression rhs, Context ctx,
+ boolean negated) {
+ if (isTypeBoolean(lhs) && isTypeBoolean(rhs)) {
+ simplifyBooleanEq(lhs, rhs, ctx, negated);
+ return;
+ }
+ }
+
+ private boolean simplifyMul(JExpression lhs, JExpression rhs, Context ctx,
+ JType type) {
+ if (isLiteralOne(rhs)) {
+ ctx.replaceMe(simplifyCast(type, lhs));
+ return true;
+ }
+ if (isLiteralOne(lhs)) {
+ ctx.replaceMe(simplifyCast(type, rhs));
+ return true;
+ }
+ if (isLiteralNegativeOne(rhs)) {
+ ctx.replaceMe(simplifyNegate(simplifyCast(type, lhs)));
+ return true;
+ }
+ if (isLiteralNegativeOne(lhs)) {
+ ctx.replaceMe(simplifyNegate(simplifyCast(type, rhs)));
+ return true;
+ }
+ if (isLiteralZero(rhs) && !lhs.hasSideEffects()) {
+ ctx.replaceMe(simplifyCast(type, rhs));
+ return true;
+ }
+ if (isLiteralZero(lhs) && !rhs.hasSideEffects()) {
+ ctx.replaceMe(simplifyCast(type, lhs));
+ return true;
+ }
+ return false;
+ }
+
+ private JExpression simplifyNegate(JExpression exp) {
+ return simplifyNegate(null, exp);
+ }
+
+ /**
+ * Simplify the expression <code>-exp</code>.
+ *
+ * @param exp The expression to negate.
+ * @param An expression equivalent to <code>-exp</code>.
+ *
+ * @return A simplified expression equivalent to <code>- exp</code>.
+ */
+ private JExpression simplifyNegate(JExpression original, JExpression exp) {
+ // - -x -> x
+ if (exp instanceof JPrefixOperation) {
+ JPrefixOperation prefarg = (JPrefixOperation) exp;
+ if (prefarg.getOp() == JUnaryOperator.NEG) {
+ return prefarg.getArg();
+ }
+ }
+
+ // no change
+ if (original != null) {
+ return original;
+ }
+ return new JPrefixOperation(program, exp.getSourceInfo(),
+ JUnaryOperator.NEG, exp);
+ }
+
+ private boolean simplifySub(JExpression lhs, JExpression rhs, Context ctx,
+ JType type) {
+ if (isLiteralZero(rhs)) {
+ ctx.replaceMe(simplifyCast(type, lhs));
+ return true;
+ }
+ if (isLiteralZero(lhs)) {
+ ctx.replaceMe(simplifyNegate(simplifyCast(type, rhs)));
+ return true;
+ }
+ return false;
+ }
+
+ private void simplifyXor(JExpression lhs, JBooleanLiteral rhs, Context ctx) {
+ if (rhs.getValue()) {
+ ctx.replaceMe(new JPrefixOperation(program, lhs.getSourceInfo(),
+ JUnaryOperator.NOT, lhs));
+ } else {
+ ctx.replaceMe(lhs);
+ }
+ }
+
+ /**
+ * Simplify XOR expressions.
+ *
+ * <pre>
+ * true ^ x -> !x
+ * false ^ x -> x
+ * y ^ true -> !y
+ * y ^ false -> y
+ * </pre>
+ */
+ private void simplifyXor(JExpression lhs, JExpression rhs, Context ctx) {
+ if (lhs instanceof JBooleanLiteral) {
+ JBooleanLiteral booleanLiteral = (JBooleanLiteral) lhs;
+ simplifyXor(rhs, booleanLiteral, ctx);
+ } else if (rhs instanceof JBooleanLiteral) {
+ JBooleanLiteral booleanLiteral = (JBooleanLiteral) rhs;
+ simplifyXor(lhs, booleanLiteral, ctx);
+ }
+ }
+
+ private boolean toBoolean(JValueLiteral x) {
+ return ((JBooleanLiteral) x).getValue();
+ }
+
+ /**
+ * Cast a Java wrapper class (Integer, Double, Float, etc.) to a double.
+ */
+ private double toDouble(JValueLiteral literal) {
+ Object valueObj = literal.getValueObj();
+ if (valueObj instanceof Number) {
+ return ((Number) valueObj).doubleValue();
+ } else {
+ return ((Character) valueObj).charValue();
+ }
+ }
+
+ private float toFloat(JValueLiteral x) {
+ return (float) toDouble(x);
+ }
+
+ /**
+ * Cast a Java wrapper class (Integer, Double, Float, etc.) to a long.
+ */
+ private int toInt(JValueLiteral literal) {
+ Object valueObj = literal.getValueObj();
+ if (valueObj instanceof Number) {
+ return ((Number) valueObj).intValue();
+ } else {
+ return ((Character) valueObj).charValue();
+ }
+ }
+
+ /**
+ * Cast a Java wrapper class (Integer, Double, Float, etc.) to a long.
+ */
+ private long toLong(JValueLiteral literal) {
+ Object valueObj = literal.getValueObj();
+ if (valueObj instanceof Number) {
+ return ((Number) valueObj).longValue();
+ } else {
+ return ((Character) valueObj).charValue();
+ }
+ }
+
private JLiteral tryGetConstant(JVariableRef x) {
if (!lvalues.contains(x)) {
return x.getTarget().getConstInitializer();
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 6fb3a68..f027b4d 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
@@ -36,6 +36,7 @@
import com.google.gwt.dev.jjs.ast.JContinueStatement;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JDoStatement;
+import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JExpressionStatement;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JFieldRef;
@@ -53,6 +54,7 @@
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNewArray;
import com.google.gwt.dev.jjs.ast.JNewInstance;
+import com.google.gwt.dev.jjs.ast.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
@@ -405,6 +407,33 @@
JsBinaryOperator myOp = JavaToJsOperatorMap.get(x.getOp());
/*
+ * Optimize null tests. We cannot do this with strings, however, because
+ * in JavaScript the empty string evaluates to boolean false.
+ *
+ * (foo == null) => !foo
+ *
+ * (foo != null) => !!foo (coerces to boolean)
+ */
+ if ((x.getOp() == JBinaryOperator.EQ)
+ || (x.getOp() == JBinaryOperator.NEQ)) {
+ boolean lhsNull = x.getLhs() instanceof JNullLiteral;
+ boolean rhsNull = x.getRhs() instanceof JNullLiteral;
+ if (lhsNull || rhsNull) {
+ JExpression toUse = lhsNull ? x.getRhs() : x.getLhs();
+ JsExpression toUseJs = lhsNull ? rhs : lhs;
+ if (!couldBeString(toUse)) {
+ if ((x.getOp() == JBinaryOperator.EQ)) {
+ push(new JsPrefixOperation(JsUnaryOperator.NOT, toUseJs));
+ } else {
+ push(new JsPrefixOperation(JsUnaryOperator.NOT,
+ new JsPrefixOperation(JsUnaryOperator.NOT, toUseJs)));
+ }
+ return;
+ }
+ }
+ }
+
+ /*
* Use === and !== on reference types, or else you can get wrong answers
* when Object.toString() == 'some string'.
*/
@@ -1192,6 +1221,18 @@
return false;
}
+ /**
+ * Return whether the expression could possibly evaluate to a string.
+ */
+ private boolean couldBeString(JExpression x) {
+ JType type = x.getType();
+ if (type instanceof JReferenceType) {
+ return typeOracle.canTheoreticallyCast((JReferenceType) type,
+ program.getTypeJavaLangString());
+ }
+ return false;
+ }
+
private JsExpression createAssignment(JsExpression lhs, JsExpression rhs) {
return new JsBinaryOperation(JsBinaryOperator.ASG, lhs, rhs);
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java b/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java
index daaa3b3..79f6f08 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsStaticEval.java
@@ -30,8 +30,10 @@
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsIf;
import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsPrefixOperation;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsUnaryOperator;
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.js.ast.JsWhile;
@@ -215,11 +217,11 @@
* TODO: borrow more concepts from
* {@link com.google.gwt.dev.jjs.impl.DeadCodeElimination}, such as ignored
* expression results.
- *
- * TODO: remove silly stuff like !! coercion in a boolean context.
*/
private class StaticEvalVisitor extends JsModVisitor {
+ private Set<JsExpression> evalBooleanContext = new HashSet<JsExpression>();
+
@Override
public void endVisit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
JsBinaryOperator op = x.getOperator();
@@ -283,6 +285,8 @@
@Override
public void endVisit(JsConditional x, JsContext<JsExpression> ctx) {
+ evalBooleanContext.remove(x.getTestExpression());
+
JsExpression condExpr = x.getTestExpression();
JsExpression thenExpr = x.getThenExpression();
JsExpression elseExpr = x.getElseExpression();
@@ -307,6 +311,8 @@
*/
@Override
public void endVisit(JsDoWhile x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.remove(x.getCondition());
+
JsExpression expr = x.getCondition();
if (expr instanceof CanBooleanEval) {
CanBooleanEval cond = (CanBooleanEval) expr;
@@ -342,6 +348,8 @@
*/
@Override
public void endVisit(JsFor x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.remove(x.getCondition());
+
JsExpression expr = x.getCondition();
if (expr instanceof CanBooleanEval) {
CanBooleanEval cond = (CanBooleanEval) expr;
@@ -370,6 +378,8 @@
*/
@Override
public void endVisit(JsIf x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.remove(x.getIfExpr());
+
JsExpression expr = x.getIfExpr();
JsStatement thenStmt = x.getThenStmt();
JsStatement elseStmt = x.getElseStmt();
@@ -405,10 +415,33 @@
}
/**
+ * Change !!x to x in a boolean context.
+ */
+ @Override
+ public void endVisit(JsPrefixOperation x, JsContext<JsExpression> ctx) {
+ if (x.getOperator() == JsUnaryOperator.NOT) {
+ evalBooleanContext.remove(x.getArg());
+ }
+
+ if (evalBooleanContext.contains(x)) {
+ if ((x.getOperator() == JsUnaryOperator.NOT)
+ && (x.getArg() instanceof JsPrefixOperation)) {
+ JsPrefixOperation arg = (JsPrefixOperation) x.getArg();
+ if (arg.getOperator() == JsUnaryOperator.NOT) {
+ ctx.replaceMe(arg.getArg());
+ return;
+ }
+ }
+ }
+ }
+
+ /**
* Prune while (false) statements.
*/
@Override
public void endVisit(JsWhile x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.remove(x.getCondition());
+
JsExpression expr = x.getCondition();
if (expr instanceof CanBooleanEval) {
CanBooleanEval cond = (CanBooleanEval) expr;
@@ -426,6 +459,44 @@
}
}
+ @Override
+ public boolean visit(JsConditional x, JsContext<JsExpression> ctx) {
+ evalBooleanContext.add(x.getTestExpression());
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsDoWhile x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.add(x.getCondition());
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsFor x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.add(x.getCondition());
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsIf x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.add(x.getIfExpr());
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsPrefixOperation x, JsContext<JsExpression> ctx) {
+ if (x.getOperator() == JsUnaryOperator.NOT) {
+ evalBooleanContext.add(x.getArg());
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsWhile x, JsContext<JsStatement> ctx) {
+ evalBooleanContext.add(x.getCondition());
+ return true;
+ }
+
/**
* This method MUST be called whenever any statements are removed from a
* function. This is because some statements, such as JsVars or JsFunction
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsIf.java b/dev/core/src/com/google/gwt/dev/js/ast/JsIf.java
index 123d6ae..fccefae 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsIf.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsIf.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -16,7 +16,7 @@
package com.google.gwt.dev.js.ast;
/**
- * Reprents a JavaScript if statement.
+ * Represents a JavaScript if statement.
*/
public final class JsIf extends JsStatement {
@@ -29,6 +29,12 @@
public JsIf() {
}
+ public JsIf(JsExpression ifExpr, JsStatement thenStmt, JsStatement elseStmt) {
+ this.ifExpr = ifExpr;
+ this.thenStmt = thenStmt;
+ this.elseStmt = elseStmt;
+ }
+
public JsStatement getElseStmt() {
return elseStmt;
}
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index 9f4519f..9cec9d4 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -27,6 +27,7 @@
import com.google.gwt.dev.jjs.test.HostedTest;
import com.google.gwt.dev.jjs.test.InnerClassTest;
import com.google.gwt.dev.jjs.test.InnerOuterSuperTest;
+import com.google.gwt.dev.jjs.test.JStaticEvalTest;
import com.google.gwt.dev.jjs.test.JsStaticEvalTest;
import com.google.gwt.dev.jjs.test.JsniConstructorTest;
import com.google.gwt.dev.jjs.test.JsoTest;
@@ -66,6 +67,7 @@
suite.addTestSuite(JsniConstructorTest.class);
suite.addTestSuite(JsoTest.class);
suite.addTestSuite(JsStaticEvalTest.class);
+ suite.addTestSuite(JStaticEvalTest.class);
suite.addTestSuite(MemberShadowingTest.class);
suite.addTestSuite(MethodBindTest.class);
suite.addTestSuite(MethodCallTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/test/JStaticEvalTest.java b/user/test/com/google/gwt/dev/jjs/test/JStaticEvalTest.java
new file mode 100644
index 0000000..6e1d360
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/JStaticEvalTest.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2008 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.test;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests that static evaluation, mainly in
+ * {@link com.google.gwt.dev.jjs.impl.DeadCodeElimination DeadCodeElimination},
+ * does not go wrong.
+ *
+ * This test does not verify that static evaluation is happening, merely that it
+ * does not make incorrect changes. To verify that static eval is happening, run
+ * with <code>-Dgwt.jjs.traceMethods=JStaticEvalTest.*</code> . All calls to
+ * assert should become trivial things like <code>assertEquals("", 5, 5)</code>.
+ *
+ * To verify the test itself, which includes a lot of random constants, run in
+ * hosted mode.
+ */
+public class JStaticEvalTest extends GWTTestCase {
+ private static void assertEquals(double expected, double actual) {
+ assertEquals(expected, actual, 0.0001);
+ }
+
+ private volatile double fieldDoubleFive = 5.0;
+ private volatile float fieldFloatFive = 5.0F;
+ private volatile int[] fieldIntArray = new int[10];
+ private volatile int fieldIntFive = 5;
+ private volatile long fieldLongFive = 5L;
+ private volatile Object fieldObject = new Object();
+ private volatile boolean fieldTrue = true;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.dev.jjs.CompilerSuite";
+ }
+
+ /**
+ * Test "true == booleanField" and permutations, as well as "true == false"
+ * and permutations.
+ */
+ public void testEqualsBool() {
+ assertTrue(fieldTrue == returnTrue());
+ assertTrue(returnTrue() == fieldTrue);
+ assertFalse(fieldTrue == returnFalse());
+ assertFalse(returnFalse() == fieldTrue);
+ assertTrue(fieldTrue != returnFalse());
+ assertTrue(returnFalse() != fieldTrue);
+ assertFalse(fieldTrue != returnTrue());
+ assertFalse(returnTrue() != fieldTrue);
+ assertTrue(returnTrue() & returnTrue());
+ assertTrue(returnTrue() | returnTrue());
+ }
+
+ /**
+ * Tests equality on literals.
+ */
+ public void testEqualsLit() {
+ assertTrue(returnTrue() == returnTrue());
+ assertFalse(returnTrue() == returnFalse());
+ assertFalse(returnFalse() == returnTrue());
+ assertTrue(returnFalse() == returnFalse());
+ assertFalse(returnTrue() != returnTrue());
+ assertTrue(returnTrue() != returnFalse());
+ assertTrue(returnFalse() != returnTrue());
+ assertFalse(returnFalse() != returnFalse());
+
+ assertTrue(returnIntFive() == returnIntFive());
+ assertFalse(returnIntFive() != returnIntFive());
+ assertTrue(returnDoubleOneHalf() == returnDoubleOneHalf());
+ assertFalse(returnDoubleOneHalf() != returnDoubleOneHalf());
+ }
+
+ /**
+ * Test rewriting if (!x) a else b to if(x) b else a. Likewise for conditional
+ * expressions.
+ */
+ public void testFlippedIf() {
+ String branch;
+ if (!fieldTrue) {
+ branch = "A";
+ } else {
+ branch = "B";
+ }
+ assertEquals("B", branch);
+ assertEquals("B", !fieldTrue ? "A" : "B");
+ }
+
+ /**
+ * Tests constant folding.
+ */
+ public void testOpsOnLiterals() {
+ assertEquals(10, returnIntFive() + returnIntFive());
+ assertEquals(0, returnIntFive() - returnIntFive());
+ assertEquals(25, returnIntFive() * returnIntFive());
+ assertEquals(1, returnIntFive() / returnIntFive());
+ assertEquals(3, returnIntThree() % returnIntFive());
+ assertEquals(96, returnIntThree() << returnIntFive());
+ assertEquals(0, returnIntThree() >> returnIntFive());
+ assertEquals(134217727, (-returnIntThree()) >>> returnIntFive());
+ assertEquals(7, returnIntFive() | returnIntThree());
+ assertEquals(1, returnIntFive() & returnIntThree());
+ assertEquals(0, returnIntFive() ^ returnIntFive());
+
+ assertEquals(1.0, returnDoubleOneHalf() + returnDoubleOneHalf());
+ assertEquals(0.0, returnDoubleOneHalf() - returnDoubleOneHalf());
+ assertEquals(0.25, returnDoubleOneHalf() * returnDoubleOneHalf());
+ assertEquals(1.0, returnDoubleOneHalf() / returnDoubleOneHalf());
+ assertEquals(0.5, returnDoubleOneHalf() % returnDoubleFive());
+
+ assertTrue(returnIntFive() == returnIntFive());
+ assertTrue(returnLongFive() == returnLongFive());
+ assertTrue(returnFloatFive() == returnFloatFive());
+ assertTrue(returnDoubleFive() == returnDoubleFive());
+ assertTrue(returnCharFive() == returnCharFive());
+ assertTrue(returnIntFive() == returnFloatFive());
+
+ assertFalse(returnIntFive() != returnIntFive());
+ assertFalse(returnLongFive() != returnLongFive());
+ assertFalse(returnFloatFive() != returnFloatFive());
+ assertFalse(returnDoubleFive() != returnDoubleFive());
+ assertFalse(returnCharFive() != returnCharFive());
+ assertFalse(returnIntFive() != returnFloatFive());
+
+ assertTrue(returnTrue() || returnFalse());
+ assertFalse(returnTrue() && returnFalse());
+ assertFalse(returnTrue() ^ returnTrue());
+
+ assertFalse(returnIntFive() < returnIntThree());
+ assertFalse(returnIntFive() <= returnIntThree());
+ assertTrue(returnIntFive() > returnIntThree());
+ assertTrue(returnIntFive() >= returnIntThree());
+
+ assertTrue(returnDoubleOneHalf() < returnDoubleFive());
+ assertTrue(returnDoubleOneHalf() <= returnDoubleFive());
+ assertFalse(returnDoubleOneHalf() > returnDoubleFive());
+ assertFalse(returnDoubleOneHalf() >= returnDoubleFive());
+
+ assertEquals(10, returnIntFive() + returnCharFive());
+ assertTrue(returnIntThree() < returnCharFive());
+
+ assertEquals(-5, -returnIntFive());
+ assertEquals(-5L, -returnLongFive());
+ assertEquals(-5.0, -returnFloatFive());
+ assertEquals(-5.0, -returnDoubleFive());
+ assertEquals(-10000000000000000000.0, -returnBigDouble());
+
+ assertFalse(!returnTrue());
+ assertTrue(!returnFalse());
+
+ assertEquals(-6, ~returnIntFive());
+ assertEquals(-6L, ~returnLongFive());
+
+ assertEquals(65536, ((char) returnIntNegOne()) + 1);
+ assertEquals(10.0, ((double) returnIntFive()) + ((double) returnIntFive()));
+ }
+
+ /**
+ * Test various useless operations like x+0 and x*1.
+ */
+ public void testUselessOps() {
+ assertEquals(5, fieldIntFive + returnIntZero());
+ assertEquals(5L, fieldLongFive + returnIntZero());
+ assertEquals(5.0, fieldFloatFive + returnIntZero());
+ assertEquals(5.0, fieldDoubleFive + returnIntZero());
+ assertEquals(5.0, fieldDoubleFive + returnCharZero());
+
+ assertEquals(5, returnIntZero() + fieldIntFive);
+ assertEquals(5L, returnIntZero() + fieldLongFive);
+ assertEquals(5.0, returnIntZero() + fieldFloatFive);
+ assertEquals(5.0, returnIntZero() + fieldDoubleFive);
+ assertEquals(5.0, returnCharZero() + fieldDoubleFive);
+
+ assertEquals(5, fieldIntFive - returnIntZero());
+ assertEquals(5L, fieldLongFive - returnIntZero());
+ assertEquals(5.0, fieldFloatFive - returnIntZero());
+ assertEquals(5.0, fieldDoubleFive - returnIntZero());
+
+ assertEquals(-5, returnIntZero() - fieldIntFive);
+ assertEquals(-5L, returnIntZero() - fieldLongFive);
+ assertEquals(-5.0, returnDoubleZero() - fieldLongFive);
+ assertEquals(-5.0, returnIntZero() - fieldFloatFive);
+ assertEquals(-5.0, returnIntZero() - fieldDoubleFive);
+
+ assertEquals(5, fieldIntFive * returnIntOne());
+ assertEquals(5L, fieldLongFive * returnLongOne());
+ assertEquals(5.0, fieldFloatFive * returnFloatOne());
+ assertEquals(5.0, fieldDoubleFive * returnDoubleOne());
+
+ assertEquals(5, fieldIntFive / returnIntOne());
+ assertEquals(5L, fieldLongFive / returnLongOne());
+ assertEquals(5.0, fieldFloatFive / returnFloatOne());
+ assertEquals(5.0, fieldDoubleFive / returnDoubleOne());
+
+ assertEquals(-5, fieldIntFive * -returnIntOne());
+ assertEquals(-5L, fieldLongFive * -returnLongOne());
+ assertEquals(-5.0, fieldFloatFive * -returnFloatOne());
+ assertEquals(-5.0, fieldDoubleFive * -returnDoubleOne());
+ assertEquals(-327675, fieldIntFive * -returnCharNegOne());
+
+ assertEquals(-5, fieldIntFive / -returnIntOne());
+ assertEquals(-5L, fieldLongFive / -returnLongOne());
+ assertEquals(-5.0, fieldFloatFive / -returnFloatOne());
+ assertEquals(-5.0, fieldDoubleFive / -returnDoubleOne());
+
+ assertEquals(5, -returnMinusFieldIntFive());
+ assertEquals(5L, -returnMinusFieldLongFive());
+ assertEquals(5.0, -returnMinusFieldFloatFive());
+ assertEquals(5.0, -returnMinusFieldDoubleFive());
+
+ assertEquals(5, fieldIntFive << returnIntZero());
+ assertEquals(5L, fieldLongFive << returnIntZero());
+
+ assertEquals(5, fieldIntFive >> returnIntZero());
+ assertEquals(5L, fieldLongFive >> returnIntZero());
+
+ assertEquals(5, fieldIntFive >>> returnIntZero());
+ assertEquals(5L, fieldLongFive >>> returnIntZero());
+
+ assertTrue(!returnNotFieldTrue());
+
+ assertTrue(fieldTrue ^ returnFalse());
+ assertFalse(fieldTrue ^ returnTrue());
+ assertFalse(returnTrue() ^ fieldTrue);
+ assertTrue(returnFalse() ^ fieldTrue);
+
+ assertEquals(0.0, fieldIntFive * returnDoubleZero());
+
+ // do not simplify x*0 if x has a side effect
+ try {
+ assertEquals(0.0, throwError() * returnDoubleZero());
+ fail("Expected an exception");
+ } catch (Error e) {
+ }
+
+ assertEquals(0.0, returnDoubleZero() * fieldIntFive);
+
+ // do not simplify 0*x if x has a side effect
+ try {
+ assertEquals(0.0, returnDoubleZero() * throwError());
+ fail("Expected an exception");
+ } catch (Error e) {
+ }
+
+ assertTrue(fieldIntArray != null);
+ assertFalse(fieldIntArray == null);
+ if (fieldIntArray == null) {
+ fail();
+ }
+ if (fieldIntArray != null) {
+ } else {
+ fail();
+ }
+
+ // do not simplify foo==null if foo can be a string
+ assertTrue(returnEmptyString() != null);
+ assertFalse(returnEmptyString() == null);
+ if (returnEmptyString() == null) {
+ fail();
+ }
+ assertFalse(fieldObject == null);
+ if (fieldObject == null) {
+ fail();
+ }
+ }
+
+ // All of these returnFoo() methods exist so that the
+ // JDT will not be able to statically evaluate with
+ // the returned values. These simple methods will be
+ // inlined by GWT, though, thus giving its static
+ // evaluation a chance to run.
+
+ /**
+ * Return a double too large to fit in a long.
+ */
+ private double returnBigDouble() {
+ return 10000000000000000000.0;
+ }
+
+ private char returnCharFive() {
+ return (char) 5;
+ }
+
+ private char returnCharNegOne() {
+ return (char) -1;
+ }
+
+ private char returnCharZero() {
+ return (char) 0;
+ }
+
+ private double returnDoubleFive() {
+ return 5.0;
+ }
+
+ private double returnDoubleOne() {
+ return 1.0;
+ }
+
+ private double returnDoubleOneHalf() {
+ return 0.5;
+ }
+
+ private double returnDoubleZero() {
+ return 0.0;
+ }
+
+ private String returnEmptyString() {
+ return "";
+ }
+
+ private boolean returnFalse() {
+ return false;
+ }
+
+ private float returnFloatFive() {
+ return 5.0F;
+ }
+
+ private float returnFloatOne() {
+ return 1.0F;
+ }
+
+ private int returnIntFive() {
+ return 5;
+ }
+
+ private int returnIntNegOne() {
+ return -1;
+ }
+
+ private int returnIntOne() {
+ return 1;
+ }
+
+ private int returnIntThree() {
+ return 3;
+ }
+
+ private int returnIntZero() {
+ return 0;
+ }
+
+ private long returnLongFive() {
+ return 5L;
+ }
+
+ private long returnLongOne() {
+ return 1L;
+ }
+
+ private double returnMinusFieldDoubleFive() {
+ return -fieldDoubleFive;
+ }
+
+ private float returnMinusFieldFloatFive() {
+ return -fieldFloatFive;
+ }
+
+ private int returnMinusFieldIntFive() {
+ return -fieldIntFive;
+ }
+
+ private long returnMinusFieldLongFive() {
+ return -fieldLongFive;
+ }
+
+ private boolean returnNotFieldTrue() {
+ return !fieldTrue;
+ }
+
+ private boolean returnTrue() {
+ return true;
+ }
+
+ private int throwError() {
+ throw new Error();
+ }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/JsStaticEvalTest.java b/user/test/com/google/gwt/dev/jjs/test/JsStaticEvalTest.java
index a4e5ac2..1f58ab5 100644
--- a/user/test/com/google/gwt/dev/jjs/test/JsStaticEvalTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/JsStaticEvalTest.java
@@ -19,11 +19,14 @@
import com.google.gwt.junit.client.GWTTestCase;
/**
- * Tests that declarations in pruned code still happen. NOTE: this test does not
- * run in hosted mode due to browser inconsistencies; however it should run in
- * web mode due to our normalizations.
+ * Most of these tests (the "do" ones guarded by isScript tests) verify that
+ * declarations in pruned code still happen. Those tests do not run reliably in
+ * hosted mode due to browser inconsistencies; however it should run in web mode
+ * due to our normalizations.
*/
public class JsStaticEvalTest extends GWTTestCase {
+ @SuppressWarnings("unused")
+ private static volatile boolean TRUE = true;
@Override
public String getModuleName() {
@@ -90,6 +93,11 @@
}
}
+ public native void testTripleNegate() /*-{
+ @junit.framework.Assert::assertFalse(Z)(
+ !!!@com.google.gwt.dev.jjs.test.JsStaticEvalTest::TRUE);
+ }-*/;
+
public void testWhileFalse() {
if (GWT.isScript()) {
doTestWhileFalse();