| /* |
| * 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.impl; |
| |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperation; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperator; |
| import com.google.gwt.dev.jjs.ast.JBlock; |
| import com.google.gwt.dev.jjs.ast.JBooleanLiteral; |
| import com.google.gwt.dev.jjs.ast.JCastOperation; |
| import com.google.gwt.dev.jjs.ast.JConditional; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JExpressionStatement; |
| import com.google.gwt.dev.jjs.ast.JIfStatement; |
| import com.google.gwt.dev.jjs.ast.JPrefixOperation; |
| import com.google.gwt.dev.jjs.ast.JPrimitiveType; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JStatement; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JUnaryOperator; |
| import com.google.gwt.dev.jjs.ast.JValueLiteral; |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| |
| import java.util.List; |
| |
| /** |
| * Methods that both construct and try to simplify AST nodes. If simplification |
| * fails, then the methods will return an original, unmodified version of the |
| * node if one is supplied. The routines do not recurse into their arguments; |
| * the arguments are assumed to already be simplified as much as possible. |
| */ |
| public class Simplifier { |
| |
| /** |
| * Negate the supplied expression if negating it makes the expression shorter. |
| * Otherwise, return null. |
| */ |
| private static JExpression maybeGetNegatedExpressionArgument(JExpression expression) { |
| if (expression instanceof JPrefixOperation) { |
| JPrefixOperation prefixOperation = (JPrefixOperation) expression; |
| if (prefixOperation.getOp() == JUnaryOperator.NOT |
| // Don't flip negations on floating point comparisons |
| && !isFloatingPointComparison(prefixOperation.getArg())) { |
| return prefixOperation.getArg(); |
| } |
| } |
| return null; |
| } |
| |
| private static <T> List<T> allButLast(List<T> list) { |
| return list.subList(0, list.size() - 1); |
| } |
| |
| private static <T> T last(List<T> list) { |
| return list.get(list.size() - 1); |
| } |
| |
| /** |
| * This class provides only static methods. No instances will ever be created. |
| */ |
| private Simplifier() { |
| } |
| |
| /** |
| * Simplify cast operations. Used when creating a cast in DeadCodeElimination. For simplifying |
| * casts that are actually in the AST, cast(JCastOperation) is used instead. |
| * |
| * <pre> |
| * (int) 1 -> 1 |
| * (A) (a,b) -> (a, (A) b) |
| * </pre> |
| * |
| * @param type the Type to cast the expression <code>exp</code> to. |
| * @param exp the current JExpression under the cast as it is being simplified. |
| * @return the simplified expression. |
| */ |
| public static JExpression cast(JType type, JExpression exp) { |
| return simplifyCast(new JCastOperation(exp.getSourceInfo(), type, exp)); |
| } |
| |
| /** |
| * Simplify cast operations. |
| * |
| * <pre> |
| * (int) 1 -> 1 |
| * (A) (a,b) -> (a, (A) b) |
| * </pre> |
| * |
| * @param castExpression a JCastOperation to be simplified. |
| * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. |
| */ |
| public static JExpression simplifyCast(JCastOperation castExpression) { |
| SourceInfo info = castExpression.getSourceInfo(); |
| JType type = castExpression.getCastType(); |
| JExpression argument = castExpression.getExpr(); |
| |
| if (type == argument.getType()) { |
| return argument; |
| } |
| |
| if (argument instanceof JMultiExpression) { |
| // (T) (a, b, c) -> (a, b,(T) c) |
| JMultiExpression argumentAsMultiExpression = (JMultiExpression) argument; |
| JMultiExpression simplifiedExpression = new JMultiExpression(info); |
| simplifiedExpression.addExpressions(allButLast(argumentAsMultiExpression.getExpressions())); |
| simplifiedExpression.addExpressions( |
| simplifyCast( |
| new JCastOperation(info, type, last(argumentAsMultiExpression.getExpressions())))); |
| return simplifiedExpression; |
| } |
| |
| if (type.isPrimitiveType() && (argument instanceof JValueLiteral)) { |
| // Statically evaluate casting literals. |
| JPrimitiveType primitiveType = (JPrimitiveType) type; |
| JValueLiteral valueLiteral = (JValueLiteral) argument; |
| JValueLiteral simplifiedExpression = primitiveType.coerce(valueLiteral); |
| if (simplifiedExpression != null) { |
| return simplifiedExpression; |
| } |
| } |
| |
| /* |
| * Discard casts from byte or short to int, because such casts are always |
| * implicit anyway. Cannot coerce char since that would change the semantics |
| * of concat. |
| */ |
| if (type == JPrimitiveType.INT) { |
| if ((argument.getType() == JPrimitiveType.SHORT) |
| || (argument.getType() == JPrimitiveType.BYTE)) { |
| return argument; |
| } |
| } |
| |
| // no simplification made |
| return castExpression; |
| } |
| |
| /** |
| * Simplify conditional expressions. |
| * |
| * <pre> |
| * (a,b,c)?d:e -> a,b,(c?d:e) |
| * true ? then : else -> then |
| * false ? then : else -> else |
| * cond ? true : else) -> cond || else |
| * cond ? false : else -> !cond && else |
| * cond ? then : true -> !cond || then |
| * cond ? then : false -> cond && then |
| * !cond ? then : else -> cond ? else : then |
| * </pre> |
| * |
| * @param expression a JCondintional to be simplified. |
| * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. |
| */ |
| public static JExpression simplifyConditional(JConditional expression) { |
| SourceInfo info = expression.getSourceInfo(); |
| JType type = expression.getType(); |
| JExpression conditionExpression = expression.getIfTest(); |
| JExpression thenExpression = expression.getThenExpr(); |
| JExpression elseExpression = expression.getElseExpr(); |
| if (conditionExpression instanceof JMultiExpression) { |
| // (a,b,c)?d:e -> a,b,(c?d:e) |
| JMultiExpression conditionMultiExpression = (JMultiExpression) conditionExpression; |
| JMultiExpression simplifiedExpression = new JMultiExpression(info); |
| simplifiedExpression.addExpressions(allButLast(conditionMultiExpression.getExpressions())); |
| simplifiedExpression.addExpressions( |
| simplifyConditional( |
| new JConditional(info, type, last(conditionMultiExpression.getExpressions()), |
| thenExpression, elseExpression))); |
| return simplifiedExpression; |
| } |
| if (conditionExpression instanceof JBooleanLiteral) { |
| return ((JBooleanLiteral) conditionExpression).getValue() |
| // e.g. (true ? then : else) -> then |
| ? thenExpression |
| // e.g. (false ? then : else) -> else |
| : elseExpression; |
| } |
| |
| if (thenExpression instanceof JBooleanLiteral) { |
| return ((JBooleanLiteral) thenExpression).getValue() |
| // e.g. (cond ? true : else) -> cond || else |
| ? or(info, conditionExpression, elseExpression) |
| // e.g. (cond ? false : else) -> !cond && else |
| : and(info, |
| negate(conditionExpression.getSourceInfo(), conditionExpression), |
| elseExpression); |
| } |
| |
| if (elseExpression instanceof JBooleanLiteral) { |
| return ((JBooleanLiteral) elseExpression).getValue() |
| // e.g. (cond ? then : true) -> !cond || then |
| ? or(info, negate(conditionExpression.getSourceInfo(), conditionExpression), thenExpression) |
| // e.g. (cond ? then : false) -> cond && then |
| : and(info, conditionExpression, thenExpression); |
| } |
| |
| // e.g. (!cond ? then : else) -> (cond ? else : then) |
| JExpression negatedExpressionArgument = maybeGetNegatedExpressionArgument(conditionExpression); |
| if (negatedExpressionArgument != null) { |
| return simplifyConditional( |
| new JConditional(info, type, negatedExpressionArgument, elseExpression, thenExpression)); |
| } |
| |
| // Not simplified. |
| return expression; |
| } |
| |
| /** |
| * Simplifies an if then else statement. |
| * |
| * <pre> |
| * if(a,b,c) d [else e] -> {a; b; if(c) d [else e]; } |
| * if(true) a [else b] -> a |
| * if(false) a else b -> b |
| * if(notImpl(c)) a else b -> if(c) b else a |
| * if(true) ; else b -> true |
| * if(false) a [else ;] -> false |
| * if(c) ; [else ;] -> c |
| *</pre> |
| * |
| * @param ifStatement the statement to simplify. |
| * @param methodReturnType the return type of the method where the statement resides if any |
| * @return the simplified statement if a simplification could be done and <code>ifStatement</code> |
| * otherwise. |
| */ |
| public static JStatement simplifyIfStatement(JIfStatement ifStatement, JType methodReturnType) { |
| SourceInfo info = ifStatement.getSourceInfo(); |
| JExpression conditionExpression = ifStatement.getIfExpr(); |
| JStatement thenStmt = ifStatement.getThenStmt(); |
| JStatement elseStmt = ifStatement.getElseStmt(); |
| if (conditionExpression instanceof JMultiExpression) { |
| // if(a,b,c) d else e -> {a; b; if(c) d else e; } |
| JMultiExpression condMulti = (JMultiExpression) conditionExpression; |
| JBlock simplifiedStatement = new JBlock(info); |
| for (JExpression expr : allButLast(condMulti.getExpressions())) { |
| simplifiedStatement.addStmt(expr.makeStatement()); |
| } |
| simplifiedStatement.addStmt( |
| simplifyIfStatement( |
| new JIfStatement(info, last(condMulti.getExpressions()), thenStmt, elseStmt), |
| methodReturnType)); |
| return simplifiedStatement; |
| } |
| |
| if (conditionExpression instanceof JBooleanLiteral) { |
| boolean conditionValue = ((JBooleanLiteral) conditionExpression).getValue(); |
| if (conditionValue && !JjsUtils.isEmptyBlock(thenStmt)) { |
| // If true, replace myself with then statement |
| return thenStmt; |
| } else if (!conditionValue && !JjsUtils.isEmptyBlock(elseStmt)) { |
| // If false, replace myself with else statement |
| return elseStmt; |
| } else { |
| // just prune me. |
| return conditionExpression.makeStatement(); |
| } |
| } |
| |
| if (JjsUtils.isEmptyBlock(thenStmt) && JjsUtils.isEmptyBlock(elseStmt)) { |
| return conditionExpression.makeStatement(); |
| } |
| |
| if (!JjsUtils.isEmptyBlock(elseStmt)) { |
| // if (!cond) foo else bar -> if (cond) bar else foo |
| JExpression negationArugment = |
| Simplifier.maybeGetNegatedExpressionArgument(conditionExpression); |
| if (negationArugment != null) { |
| // Force sub-parts to blocks, otherwise we break else-if chains. |
| // TODO: this goes away when we normalize the Java AST properly. |
| thenStmt = ensureBlock(thenStmt); |
| elseStmt = ensureBlock(elseStmt); |
| return simplifyIfStatement( |
| new JIfStatement(info, negationArugment, elseStmt, thenStmt), methodReturnType); |
| } |
| } |
| |
| JStatement rewritenStatement = |
| rewriteIfStatementAsExpression( |
| info, conditionExpression, thenStmt, elseStmt, methodReturnType); |
| if (rewritenStatement != null) { |
| return rewritenStatement; |
| } |
| |
| // no simplification made |
| return ifStatement; |
| } |
| |
| /** |
| * Simplifies an negation expression. |
| * |
| * !(a > b) => a <= b |
| * |
| * @param expression the expression to simplify. |
| * @return the simplified expression if a simplification could be done and <code>expr</code> |
| * otherwise. |
| */ |
| public static JExpression simplifyNot(JPrefixOperation expression) { |
| JExpression argument = expression.getArg(); |
| if (isFloatingPointComparison(argument)) { |
| // Don't negate floating point expression because it changes the values when NaNs are |
| // involved. E.g. !(Nan > 3) is not equivalent to (Nan <= 3). |
| return expression; |
| } |
| SourceInfo info = expression.getSourceInfo(); |
| if (argument instanceof JMultiExpression) { |
| // !(a,b,c) -> (a,b,!c) |
| JMultiExpression multiExpression = (JMultiExpression) argument; |
| JMultiExpression simplifiedExpression = new JMultiExpression(info); |
| simplifiedExpression.addExpressions(allButLast(multiExpression.getExpressions())); |
| simplifiedExpression.addExpressions(negate(info, last(multiExpression.getExpressions()))); |
| return simplifiedExpression; |
| } |
| |
| if (argument instanceof JBinaryOperation) { |
| // try to invert the binary operator |
| JBinaryOperation binaryExpression = (JBinaryOperation) argument; |
| switch (binaryExpression.getOp()) { |
| case EQ: |
| // e.g. !(x == y) -> x != y |
| return new JBinaryOperation(info, binaryExpression.getType(), JBinaryOperator.NEQ, |
| binaryExpression.getLhs(), binaryExpression.getRhs()); |
| case NEQ: |
| // e.g. !(x != y) -> x == y |
| return new JBinaryOperation(info, binaryExpression.getType(), JBinaryOperator.EQ, |
| binaryExpression.getLhs(), binaryExpression.getRhs()); |
| case GT: |
| // e.g. !(x > y) -> x <= y |
| return new JBinaryOperation(info, binaryExpression.getType(), JBinaryOperator.LTE, |
| binaryExpression.getLhs(), binaryExpression.getRhs()); |
| case LTE: |
| // e.g. !(x <= y) -> x > y |
| return new JBinaryOperation(info, binaryExpression.getType(), JBinaryOperator.GT, |
| binaryExpression.getLhs(), binaryExpression.getRhs()); |
| case GTE: |
| // e.g. !(x >= y) -> x < y |
| return new JBinaryOperation(info, binaryExpression.getType(), JBinaryOperator.LT, |
| binaryExpression.getLhs(), binaryExpression.getRhs()); |
| case LT: |
| // e.g. !(x < y) -> x >= y |
| return new JBinaryOperation(info, binaryExpression.getType(), JBinaryOperator.GTE, |
| binaryExpression.getLhs(), binaryExpression.getRhs()); |
| } |
| } |
| |
| if (argument instanceof JPrefixOperation) { |
| // try to invert the unary operator |
| JPrefixOperation prefixExpression = (JPrefixOperation) argument; |
| // e.g. !!x -> x |
| if (prefixExpression.getOp() == JUnaryOperator.NOT) { |
| return prefixExpression.getArg(); |
| } |
| } |
| |
| if (argument instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) argument; |
| return JBooleanLiteral.get(!booleanLiteral.getValue()); |
| } |
| // no simplification made. |
| return expression; |
| } |
| |
| private static boolean isFloatingPointComparison(JExpression expr) { |
| if (expr instanceof JBinaryOperation) { |
| JBinaryOperation binaryOperation = (JBinaryOperation) expr; |
| return |
| binaryOperation.getType() == JPrimitiveType.BOOLEAN |
| && (binaryOperation.getLhs().getType() == JPrimitiveType.FLOAT |
| || binaryOperation.getLhs().getType() == JPrimitiveType.DOUBLE |
| || binaryOperation.getRhs().getType() == JPrimitiveType.FLOAT |
| || binaryOperation.getRhs().getType() == JPrimitiveType.DOUBLE); |
| } |
| return false; |
| } |
| |
| private static JExpression negate(SourceInfo info, JExpression argument) { |
| return simplifyNot(new JPrefixOperation(info, JUnaryOperator.NOT, argument)); |
| } |
| |
| /** |
| * Simplify short circuit AND expressions. |
| * |
| * <pre> |
| * true && isWhatever() -> isWhatever() |
| * false && isWhatever() -> false |
| * |
| * isWhatever() && true -> isWhatever() |
| * isWhatever() && false -> false, unless side effects |
| * |
| * (a, b) && c -> (a, b && c) |
| * </pre> |
| * |
| * @param expression an AND JBinaryExpression to be simplified. |
| * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. |
| * |
| */ |
| public static JExpression simplifyAnd(JBinaryOperation expression) { |
| assert expression.getOp() == JBinaryOperator.AND |
| : "Simplifier.and was called with " + expression; |
| JExpression lhs = expression.getLhs(); |
| JExpression rhs = expression.getRhs(); |
| SourceInfo info = expression.getSourceInfo(); |
| if (lhs instanceof JMultiExpression) { |
| // (a,b,c)&&d -> a,b,(c&&d) |
| JMultiExpression lhsMultiExpression = (JMultiExpression) lhs; |
| JMultiExpression simplifiedExpression = new JMultiExpression(info); |
| simplifiedExpression.addExpressions(allButLast(lhsMultiExpression.getExpressions())); |
| simplifiedExpression.addExpressions( |
| and(info, last(lhsMultiExpression.getExpressions()), rhs)); |
| return simplifiedExpression; |
| } |
| if (lhs instanceof JBooleanLiteral) { |
| return (((JBooleanLiteral) lhs).getValue()) ? rhs : lhs; |
| } |
| |
| if (rhs instanceof JBooleanLiteral) { |
| if (((JBooleanLiteral) rhs).getValue()) { |
| return lhs; |
| } else if (!lhs.hasSideEffects()) { |
| // Do not remove lhs if it had side effects |
| return rhs; |
| } |
| } |
| |
| // no simplification made. |
| return expression; |
| } |
| |
| private static JExpression and(SourceInfo info, JExpression lhs, JExpression rhs) { |
| return simplifyAnd( |
| new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.AND, lhs, rhs)); |
| } |
| |
| /** |
| * Simplify short circuit OR expressions. |
| * |
| * <pre> |
| * true || isWhatever() -> true |
| * false || isWhatever() -> isWhatever() |
| * |
| * isWhatever() || false isWhatever() |
| * isWhatever() || true -> true, unless side effects |
| * |
| * (a, b) || c -> (a, b || c) |
| * </pre> |
| * |
| * @param expression an OR JBinaryExpression to be simplified. |
| * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. |
| * |
| */ |
| public static JExpression simplifyOr(JBinaryOperation expression) { |
| assert expression.getOp() == JBinaryOperator.OR |
| : "Simplifier.or was called with " + expression; |
| JExpression lhs = expression.getLhs(); |
| JExpression rhs = expression.getRhs(); |
| SourceInfo info = expression.getSourceInfo(); |
| if (lhs instanceof JMultiExpression) { |
| // (a,b,c)|| d -> a,b,(c||d) |
| JMultiExpression lhsMultiExpression = (JMultiExpression) lhs; |
| JMultiExpression simplifiedExpression = new JMultiExpression(info); |
| simplifiedExpression.addExpressions(allButLast(lhsMultiExpression.getExpressions())); |
| simplifiedExpression.addExpressions(or(info, last(lhsMultiExpression.getExpressions()), rhs)); |
| return simplifiedExpression; |
| } |
| if (lhs instanceof JBooleanLiteral) { |
| return ((JBooleanLiteral) lhs).getValue() ? lhs : rhs; |
| } |
| |
| if (rhs instanceof JBooleanLiteral) { |
| if (!((JBooleanLiteral) rhs).getValue()) { |
| return lhs; |
| } else if (!lhs.hasSideEffects()) { |
| return rhs; |
| } |
| } |
| return expression; |
| } |
| |
| private static JExpression or(SourceInfo info, JExpression lhs, JExpression rhs) { |
| return simplifyOr( |
| new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.OR, lhs, rhs)); |
| } |
| |
| private static JStatement ensureBlock(JStatement statement) { |
| if (statement == null) { |
| return null; |
| } |
| if (statement instanceof JBlock) { |
| return statement; |
| } |
| |
| return new JBlock(statement.getSourceInfo(), statement); |
| } |
| |
| private static JExpression extractExpression(JStatement statement) { |
| if (statement instanceof JExpressionStatement) { |
| return ((JExpressionStatement) statement).getExpr(); |
| } |
| |
| return null; |
| } |
| |
| private static JStatement extractSingleStatement(JStatement statement) { |
| if (statement instanceof JBlock) { |
| JBlock block = (JBlock) statement; |
| if (block.getStatements().size() == 1) { |
| return extractSingleStatement(block.getStatements().get(0)); |
| } |
| } |
| |
| return statement; |
| } |
| |
| private static JStatement rewriteIfStatementAsExpression(SourceInfo sourceInfo, |
| JExpression conditionExpression, JStatement thenStmt, JStatement elseStmt, |
| JType methodReturnType) { |
| thenStmt = extractSingleStatement(thenStmt); |
| elseStmt = extractSingleStatement(elseStmt); |
| |
| if (thenStmt instanceof JReturnStatement && elseStmt instanceof JReturnStatement |
| && methodReturnType != null) { |
| // Special case |
| // if () { return ..; } else { return ..; } => |
| // return ... ? ... : ...; |
| JExpression thenExpression = ((JReturnStatement) thenStmt).getExpr(); |
| JExpression elseExpression = ((JReturnStatement) elseStmt).getExpr(); |
| if (thenExpression == null || elseExpression == null) { |
| // empty returns are not supported. |
| return null; |
| } |
| |
| return |
| new JConditional( |
| sourceInfo, methodReturnType, conditionExpression, thenExpression, elseExpression |
| ).makeReturnStatement(); |
| } |
| |
| if (elseStmt != null) { |
| // if () { } else { } -> ... ? ... : ... ; |
| JExpression thenExpression = extractExpression(thenStmt); |
| JExpression elseExpression = extractExpression(elseStmt); |
| |
| if (thenExpression != null && elseExpression != null) { |
| return |
| new JConditional( |
| sourceInfo, JPrimitiveType.VOID, conditionExpression, thenExpression, elseExpression |
| ).makeStatement(); |
| } |
| } else { |
| // if () { } -> ... && ...; |
| JExpression thenExpression = extractExpression(thenStmt); |
| |
| if (thenExpression != null) { |
| JBinaryOperator binaryOperator = JBinaryOperator.AND; |
| |
| JExpression negationArgument = maybeGetNegatedExpressionArgument(conditionExpression); |
| if (negationArgument != null) { |
| conditionExpression = negationArgument; |
| binaryOperator = JBinaryOperator.OR; |
| } |
| |
| return |
| new JBinaryOperation( |
| sourceInfo, JPrimitiveType.VOID, binaryOperator, conditionExpression, thenExpression |
| ).makeStatement(); |
| } |
| } |
| |
| return null; |
| } |
| } |