/* * 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.JMethod; import com.google.gwt.dev.jjs.ast.JNode; 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.JUnaryOperation; 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 { /** * TODO: if the AST were normalized, we wouldn't need this. */ public static boolean isEmpty(JStatement stmt) { if (stmt == null) { return true; } return (stmt instanceof JBlock && ((JBlock) stmt).getStatements().isEmpty()); } /** * Negate the supplied expression if negating it makes the expression shorter. * Otherwise, return null. */ private static JExpression maybeUnflipBoolean(JExpression expr) { if (expr instanceof JUnaryOperation) { JUnaryOperation unop = (JUnaryOperation) expr; if (unop.getOp() == JUnaryOperator.NOT) { return unop.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 castImpl(null, exp.getSourceInfo(), type, exp); } /** * Simplify cast operations. * * <pre> * (int) 1 -> 1 * (A) (a,b) -> (a, (A) b) * </pre> * * @param exp a JCastOperation to be simplified. * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. */ public static JExpression cast(JCastOperation exp) { return castImpl(exp, exp.getSourceInfo(), exp.getCastType(), exp.getExpr()); } private static JExpression castImpl(JExpression original, SourceInfo info, JType type, JExpression exp) { info = getBestSourceInfo(original, info, exp); if (exp instanceof JMultiExpression) { // (T)(a,b,c) -> a,b,(T) c JMultiExpression expMulti = (JMultiExpression) exp; JMultiExpression newMulti = new JMultiExpression(info); newMulti.exprs.addAll(allButLast(expMulti.exprs)); newMulti.exprs.add(castImpl(null, info, type, last(expMulti.exprs))); // TODO(rluble): immediately simplify the resulting multi. // TODO(rluble): refactor common outward JMultiExpression movement. return newMulti; } 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; } } /* * 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) { JType expType = exp.getType(); if ((expType == JPrimitiveType.SHORT) || (expType == JPrimitiveType.BYTE)) { return exp; } } // no simplification made if (original != null) { return original; } return new JCastOperation(info, type, exp); } /** * 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 exp a JCondintional to be simplified. * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. */ public static JExpression conditional(JConditional exp) { return conditionalImpl(exp, exp.getSourceInfo(), exp.getType(), exp.getIfTest(), exp.getThenExpr(), exp.getElseExpr()); } private static JExpression conditionalImpl(JConditional original, SourceInfo info, JType type, JExpression condExpr, JExpression thenExpr, JExpression elseExpr) { info = getBestSourceInfo(original, info, condExpr); if (condExpr instanceof JMultiExpression) { // (a,b,c)?d:e -> a,b,(c?d:e) // TODO(spoon): do this outward multi movement for all AST nodes JMultiExpression condMulti = (JMultiExpression) condExpr; JMultiExpression newMulti = new JMultiExpression(info); newMulti.exprs.addAll(allButLast(condMulti.exprs)); newMulti.exprs.add(conditionalImpl(null, info, type, last(condMulti.exprs), thenExpr, elseExpr)); // TODO(spoon): immediately simplify the resulting multi return newMulti; } if (condExpr instanceof JBooleanLiteral) { if (((JBooleanLiteral) condExpr).getValue()) { // e.g. (true ? then : else) -> then return thenExpr; } else { // e.g. (false ? then : else) -> else return elseExpr; } } else if (thenExpr instanceof JBooleanLiteral) { if (((JBooleanLiteral) thenExpr).getValue()) { // e.g. (cond ? true : else) -> cond || else return orImpl(null, info, condExpr, elseExpr); } else { // e.g. (cond ? false : else) -> !cond && else JExpression notCondExpr = notImpl(null, condExpr.getSourceInfo(), condExpr); return andImpl(null, info, notCondExpr, elseExpr); } } else if (elseExpr instanceof JBooleanLiteral) { if (((JBooleanLiteral) elseExpr).getValue()) { // e.g. (cond ? then : true) -> !cond || then JExpression notCondExpr = notImpl(null, condExpr.getSourceInfo(), condExpr); return orImpl(null, info, notCondExpr, thenExpr); } else { // e.g. (cond ? then : false) -> cond && then return andImpl(null, info, condExpr, thenExpr); } } else { // e.g. (!cond ? then : else) -> (cond ? else : then) JExpression unflipped = maybeUnflipBoolean(condExpr); if (unflipped != null) { return new JConditional(info, type, unflipped, elseExpr, thenExpr); } } // no simplification made if (original != null) { return original; } return new JConditional(info, type, condExpr, thenExpr, elseExpr); } /** * Simplifies an ifthenelse 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 stmt the statement to simplify. * @param currentMethod the method where the statement resides * @return the simplified statement if a simplification could be done and <code>stmt</code> * otherwise. */ public static JStatement ifStatement(JIfStatement stmt, JMethod currentMethod) { return ifStatementImpl(stmt, stmt.getSourceInfo(), stmt.getIfExpr(), stmt.getThenStmt(), stmt.getElseStmt(), currentMethod); } private static JStatement ifStatementImpl(JIfStatement original, SourceInfo info, JExpression condExpr, JStatement thenStmt,JStatement elseStmt, JMethod currentMethod) { info = getBestSourceInfo(original, info, condExpr); if (condExpr instanceof JMultiExpression) { // if(a,b,c) d else e -> {a; b; if(c) d else e; } JMultiExpression condMulti = (JMultiExpression) condExpr; JBlock newBlock = new JBlock(info); for (JExpression expr : allButLast(condMulti.exprs)) { newBlock.addStmt(expr.makeStatement()); } newBlock.addStmt(ifStatementImpl(null, info, last(condMulti.exprs), thenStmt, elseStmt, currentMethod)); // TODO(spoon): immediately simplify the resulting block return newBlock; } if (condExpr instanceof JBooleanLiteral) { JBooleanLiteral booleanLiteral = (JBooleanLiteral) condExpr; boolean boolVal = booleanLiteral.getValue(); if (boolVal && !isEmpty(thenStmt)) { // If true, replace myself with then statement return thenStmt; } else if (!boolVal && !isEmpty(elseStmt)) { // If false, replace myself with else statement return elseStmt; } else { // just prune me return condExpr.makeStatement(); } } if (isEmpty(thenStmt) && isEmpty(elseStmt)) { return condExpr.makeStatement(); } if (!isEmpty(elseStmt)) { // if (!cond) foo else bar -> if (cond) bar else foo JExpression unflipped = Simplifier.maybeUnflipBoolean(condExpr); if (unflipped != 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 ifStatementImpl(null, info, unflipped, elseStmt, thenStmt, currentMethod); } } JStatement rewritenStatement = rewriteIfIntoBoolean(info, condExpr, thenStmt, elseStmt, currentMethod); if (rewritenStatement != null) { return rewritenStatement; } // no simplification made if (original != null) { return original; } return new JIfStatement(info, condExpr, thenStmt, elseStmt); } /** * Simplifies an negation expression. * * if(a,b,c) d else e -> {a; b; if(c) d else e; } * * @param expr the expression to simplify. * @return the simplified expression if a simplification could be done and <code>expr</code> * otherwise. */ public static JExpression not(JPrefixOperation expr) { return notImpl(expr, expr.getSourceInfo(), expr.getArg()); } private static JExpression notImpl(JPrefixOperation original, SourceInfo info, JExpression arg) { info = getBestSourceInfo(original, info, arg); if (arg instanceof JMultiExpression) { // !(a,b,c) -> (a,b,!c) JMultiExpression argMulti = (JMultiExpression) arg; JMultiExpression newMulti = new JMultiExpression(info); newMulti.exprs.addAll(allButLast(argMulti.exprs)); newMulti.exprs.add(notImpl(null, info, last(argMulti.exprs))); // TODO(spoon): immediately simplify the newMulti return newMulti; } if (arg instanceof JBinaryOperation) { // try to invert the binary operator JBinaryOperation argOp = (JBinaryOperation) arg; JBinaryOperator op = argOp.getOp(); JBinaryOperator newOp = null; if (op == JBinaryOperator.EQ) { // e.g. !(x == y) -> x != y newOp = JBinaryOperator.NEQ; } else if (op == JBinaryOperator.NEQ) { // e.g. !(x != y) -> x == y newOp = JBinaryOperator.EQ; } else if (op == JBinaryOperator.GT) { // e.g. !(x > y) -> x <= y newOp = JBinaryOperator.LTE; } else if (op == JBinaryOperator.LTE) { // e.g. !(x <= y) -> x > y newOp = JBinaryOperator.GT; } else if (op == JBinaryOperator.GTE) { // e.g. !(x >= y) -> x < y newOp = JBinaryOperator.LT; } else if (op == JBinaryOperator.LT) { // e.g. !(x < y) -> x >= y newOp = JBinaryOperator.GTE; } if (newOp != null) { JBinaryOperation newBinOp = new JBinaryOperation(info, argOp.getType(), newOp, argOp.getLhs(), argOp.getRhs()); return newBinOp; } } else if (arg instanceof JPrefixOperation) { // try to invert the unary operator JPrefixOperation argOp = (JPrefixOperation) arg; JUnaryOperator op = argOp.getOp(); // e.g. !!x -> x if (op == JUnaryOperator.NOT) { return argOp.getArg(); } } else if (arg instanceof JBooleanLiteral) { JBooleanLiteral booleanLit = (JBooleanLiteral) arg; return JBooleanLiteral.get(!booleanLit.getValue()); } // no simplification made if (original != null) { return original; } return new JPrefixOperation(info, JUnaryOperator.NOT, arg); } /** * 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 exp an AND JBinaryExpression to be simplified. * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. * */ public static JExpression and(JBinaryOperation exp) { assert exp.getOp() == JBinaryOperator.AND : "Simplifier.and was called with " + exp; return andImpl(exp, null, exp.getLhs(), exp.getRhs()); } private static JExpression andImpl(JBinaryOperation original, SourceInfo info, JExpression lhs, JExpression rhs) { info = getBestSourceInfo(original, info, lhs); if (lhs instanceof JMultiExpression) { // (a,b,c)&&d -> a,b,(c&&d) JMultiExpression lhsMulti = (JMultiExpression) lhs; JMultiExpression newMulti = new JMultiExpression(info); newMulti.exprs.addAll(allButLast(lhsMulti.exprs)); newMulti.exprs.add(andImpl(null, info, last(lhsMulti.exprs), rhs)); // TODO(rluble): immediately simplify the resulting multi. // TODO(rluble): refactor common outward JMultiExpression movement. return newMulti; } if (lhs instanceof JBooleanLiteral) { JBooleanLiteral booleanLiteral = (JBooleanLiteral) lhs; if (booleanLiteral.getValue()) { return rhs; } else { return lhs; } } else if (rhs instanceof JBooleanLiteral) { JBooleanLiteral booleanLiteral = (JBooleanLiteral) rhs; if (booleanLiteral.getValue()) { return lhs; } else if (!lhs.hasSideEffects()) { return rhs; } } // no simplification made if (original != null) { return original; } return new JBinaryOperation(info, rhs.getType(), 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 exp an OR JBinaryExpression to be simplified. * @return the simplified expression if a simplification was possible; <code>exp</code> otherwise. * */ public static JExpression or(JBinaryOperation exp) { assert exp.getOp() == JBinaryOperator.OR : "Simplifier.and was called with " + exp; return orImpl(exp, null, exp.getLhs(), exp.getRhs()); } private static JExpression orImpl(JBinaryOperation original, SourceInfo info, JExpression lhs, JExpression rhs) { info = getBestSourceInfo(original, info, lhs); if (lhs instanceof JMultiExpression) { // (a,b,c)|| d -> a,b,(c||d) JMultiExpression lhsMulti = (JMultiExpression) lhs; JMultiExpression newMulti = new JMultiExpression(info); newMulti.exprs.addAll(allButLast(lhsMulti.exprs)); newMulti.exprs.add(orImpl(null, info, last(lhsMulti.exprs), rhs)); // TODO(rluble): immediately simplify the resulting multi. // TODO(rluble): refactor common outward JMultiExpression movement. return newMulti; } if (lhs instanceof JBooleanLiteral) { JBooleanLiteral booleanLiteral = (JBooleanLiteral) lhs; if (booleanLiteral.getValue()) { return lhs; } else { return rhs; } } else if (rhs instanceof JBooleanLiteral) { JBooleanLiteral booleanLiteral = (JBooleanLiteral) rhs; if (!booleanLiteral.getValue()) { return lhs; } else if (!lhs.hasSideEffects()) { return rhs; } } // no simplification made if (original != null) { return original; } return new JBinaryOperation(info, rhs.getType(), JBinaryOperator.OR, lhs, rhs); } private static JStatement ensureBlock(JStatement stmt) { if (stmt == null) { return null; } if (!(stmt instanceof JBlock)) { JBlock block = new JBlock(stmt.getSourceInfo()); block.addStmt(stmt); stmt = block; } return stmt; } private static JExpression extractExpression(JStatement stmt) { if (stmt instanceof JExpressionStatement) { JExpressionStatement statement = (JExpressionStatement) stmt; return statement.getExpr(); } return null; } private static JStatement extractSingleStatement(JStatement stmt) { if (stmt instanceof JBlock) { JBlock block = (JBlock) stmt; if (block.getStatements().size() == 1) { return extractSingleStatement(block.getStatements().get(0)); } } return stmt; } /** * Determine the best SourceInfo to use in a particular transformation. * * @param original the original node that is being transformed. Can be <code>null</code>. * @param info an explicit SourceInfo that might be used, Can be <code>null</code>. * @param defaultNode a node from where to obtain the SourceInfo. * @return a SourceInfo chosen according to the following priority info>original>default. */ private static SourceInfo getBestSourceInfo(JNode original, SourceInfo info, JNode defaultNode) { if (info == null) { if (original == null) { info = defaultNode.getSourceInfo(); } else { info = original.getSourceInfo(); } } return info; } private static JStatement rewriteIfIntoBoolean(SourceInfo sourceInfo, JExpression condExpr, JStatement thenStmt, JStatement elseStmt, JMethod currentMethod) { thenStmt = extractSingleStatement(thenStmt); elseStmt = extractSingleStatement(elseStmt); if (thenStmt instanceof JReturnStatement && elseStmt instanceof JReturnStatement && currentMethod != 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; } JConditional conditional = new JConditional(sourceInfo, currentMethod.getType(), condExpr, thenExpression, elseExpression); JReturnStatement returnStatement = new JReturnStatement(sourceInfo, conditional); return returnStatement; } if (elseStmt != null) { // if () { } else { } -> ... ? ... : ... ; JExpression thenExpression = extractExpression(thenStmt); JExpression elseExpression = extractExpression(elseStmt); if (thenExpression != null && elseExpression != null) { JConditional conditional = new JConditional(sourceInfo, JPrimitiveType.VOID, condExpr, thenExpression, elseExpression); return conditional.makeStatement(); } } else { // if () { } -> ... && ...; JExpression thenExpression = extractExpression(thenStmt); if (thenExpression != null) { JBinaryOperator binaryOperator = JBinaryOperator.AND; JExpression unflipExpression = maybeUnflipBoolean(condExpr); if (unflipExpression != null) { condExpr = unflipExpression; binaryOperator = JBinaryOperator.OR; } JBinaryOperation binaryOperation = new JBinaryOperation(sourceInfo, JPrimitiveType.VOID, binaryOperator, condExpr, thenExpression); return binaryOperation.makeStatement(); } } return null; } }