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