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