| /* |
| * 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.js; |
| |
| import com.google.gwt.dev.jjs.InternalCompilerException; |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.impl.OptimizerStats; |
| import com.google.gwt.dev.js.ast.CanBooleanEval; |
| import com.google.gwt.dev.js.ast.JsBinaryOperation; |
| import com.google.gwt.dev.js.ast.JsBinaryOperator; |
| import com.google.gwt.dev.js.ast.JsBlock; |
| import com.google.gwt.dev.js.ast.JsBooleanLiteral; |
| import com.google.gwt.dev.js.ast.JsBreak; |
| import com.google.gwt.dev.js.ast.JsConditional; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsContinue; |
| import com.google.gwt.dev.js.ast.JsDoWhile; |
| import com.google.gwt.dev.js.ast.JsEmpty; |
| import com.google.gwt.dev.js.ast.JsExprStmt; |
| import com.google.gwt.dev.js.ast.JsExpression; |
| import com.google.gwt.dev.js.ast.JsFor; |
| 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.JsNullLiteral; |
| import com.google.gwt.dev.js.ast.JsNumberLiteral; |
| 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.JsStringLiteral; |
| import com.google.gwt.dev.js.ast.JsUnaryOperation; |
| import com.google.gwt.dev.js.ast.JsUnaryOperator; |
| import com.google.gwt.dev.js.ast.JsValueLiteral; |
| import com.google.gwt.dev.js.ast.JsVars; |
| import com.google.gwt.dev.js.ast.JsVars.JsVar; |
| import com.google.gwt.dev.js.ast.JsVisitor; |
| import com.google.gwt.dev.js.ast.JsWhile; |
| import com.google.gwt.dev.js.rhino.ScriptRuntime; |
| import com.google.gwt.dev.util.Ieee754_64_Arithmetic; |
| import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; |
| |
| import java.util.ArrayList; |
| import java.util.EnumSet; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Removes JsFunctions that are never referenced in the program. |
| */ |
| public class JsStaticEval { |
| /** |
| * Examines code to find out whether it contains any break or continue |
| * statements. |
| * |
| * TODO: We could be more sophisticated with this. A nested while loop with an |
| * unlabeled break should not cause this visitor to return false. Nor should a |
| * labeled break break to another context. |
| */ |
| public static class FindBreakContinueStatementsVisitor extends JsVisitor { |
| private boolean hasBreakContinueStatements = false; |
| |
| @Override |
| public void endVisit(JsBreak x, JsContext ctx) { |
| hasBreakContinueStatements = true; |
| } |
| |
| @Override |
| public void endVisit(JsContinue x, JsContext ctx) { |
| hasBreakContinueStatements = true; |
| } |
| |
| protected boolean hasBreakContinueStatements() { |
| return hasBreakContinueStatements; |
| } |
| } |
| |
| /** |
| * Creates a minimalist list of statements that must be run in order to |
| * achieve the same declaration effect as the visited statements. |
| * |
| * For example, a JsFunction declaration should be run as a JsExprStmt. JsVars |
| * should be run without any initializers. |
| * |
| * This visitor is called from |
| * {@link StaticEvalVisitor#ensureDeclarations(JsStatement)} on any statements |
| * that are removed from a function. |
| */ |
| private static class MustExecVisitor extends JsVisitor { |
| |
| private final List<JsStatement> mustExec = new ArrayList<JsStatement>(); |
| |
| public MustExecVisitor() { |
| } |
| |
| @Override |
| public void endVisit(JsExprStmt x, JsContext ctx) { |
| JsFunction func = JsUtils.isFunctionDeclaration(x); |
| if (func != null) { |
| mustExec.add(x); |
| } |
| } |
| |
| @Override |
| public void endVisit(JsVars x, JsContext ctx) { |
| JsVars strippedVars = new JsVars(x.getSourceInfo()); |
| boolean mustReplace = false; |
| for (JsVar var : x) { |
| JsVar strippedVar = new JsVar(var.getSourceInfo(), var.getName()); |
| strippedVars.add(strippedVar); |
| if (var.getInitExpr() != null) { |
| mustReplace = true; |
| } |
| } |
| if (mustReplace) { |
| mustExec.add(strippedVars); |
| } else { |
| mustExec.add(x); |
| } |
| } |
| |
| public List<JsStatement> getStatements() { |
| return mustExec; |
| } |
| |
| @Override |
| public boolean visit(JsFunction x, JsContext ctx) { |
| // Don't dive into nested functions. |
| return false; |
| } |
| } |
| |
| /** |
| * Does static evals. |
| * |
| * TODO: borrow more concepts from |
| * {@link com.google.gwt.dev.jjs.impl.DeadCodeElimination}, such as ignored |
| * expression results. |
| */ |
| private class StaticEvalVisitor extends JsModVisitor { |
| |
| private Set<JsExpression> evalBooleanContext = new HashSet<JsExpression>(); |
| |
| /** |
| * This is used by {@link #additionCoercesToString}. |
| */ |
| private Map<JsExpression, Boolean> coercesToStringMap = new IdentityHashMap<JsExpression, Boolean>(); |
| |
| @Override |
| public void endVisit(JsBinaryOperation x, JsContext ctx) { |
| JsBinaryOperator op = x.getOperator(); |
| JsExpression arg1 = x.getArg1(); |
| JsExpression arg2 = x.getArg2(); |
| |
| JsExpression result = x; |
| |
| if (op == JsBinaryOperator.AND) { |
| result = shortCircuitAnd(x); |
| } else if (op == JsBinaryOperator.OR) { |
| result = shortCircuitOr(x); |
| } else if (op == JsBinaryOperator.COMMA) { |
| result = trySimplifyComma(x); |
| } else if (op == JsBinaryOperator.EQ || op == JsBinaryOperator.REF_EQ) { |
| result = simplifyEqAndRefEq(x); |
| } else if (op == JsBinaryOperator.NEQ || op == JsBinaryOperator.REF_NEQ) { |
| result = simplifyNeAndRefNe(x); |
| } else if (arg1 instanceof JsValueLiteral && arg2 instanceof JsValueLiteral) { |
| switch (op) { |
| case ADD: |
| case SUB: |
| case MUL: |
| case DIV: |
| case MOD: |
| case GT: |
| case GTE: |
| case LT: |
| case LTE: |
| result = simplifyOp(x); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| result = maybeReorderOperations(result); |
| |
| if (result != x) { |
| ctx.replaceMe(result); |
| } |
| } |
| |
| /** |
| * Prune dead statements and empty blocks. |
| */ |
| @Override |
| public void endVisit(JsBlock x, JsContext ctx) { |
| /* |
| * Remove any dead statements after an abrupt change in code flow and |
| * promote safe statements within nested blocks to this block. |
| */ |
| List<JsStatement> stmts = x.getStatements(); |
| for (int i = 0; i < stmts.size(); i++) { |
| JsStatement stmt = stmts.get(i); |
| |
| if (stmt instanceof JsBlock) { |
| // Promote a sub-block's children to the current block. |
| JsBlock block = (JsBlock) stmt; |
| stmts.remove(i); |
| stmts.addAll(i, block.getStatements()); |
| i--; |
| didChange = true; |
| continue; |
| } |
| |
| if (!stmt.unconditionalControlBreak()) { |
| continue; |
| } |
| |
| // Abrupt change in flow, chop the remaining items from this block |
| for (int j = i + 1; j < stmts.size();) { |
| JsStatement toRemove = stmts.get(j); |
| JsStatement toReplace = ensureDeclarations(toRemove); |
| if (toReplace == null) { |
| stmts.remove(j); |
| didChange = true; |
| } else if (toReplace == toRemove) { |
| ++j; |
| } else { |
| stmts.set(j, toReplace); |
| ++j; |
| didChange = true; |
| } |
| } |
| } |
| |
| if (ctx.canRemove() && stmts.size() == 0) { |
| // Remove blocks with no effect |
| ctx.removeMe(); |
| } |
| } |
| |
| @Override |
| public void endVisit(JsConditional x, JsContext ctx) { |
| evalBooleanContext.remove(x.getTestExpression()); |
| |
| JsExpression condExpr = x.getTestExpression(); |
| JsExpression thenExpr = x.getThenExpression(); |
| JsExpression elseExpr = x.getElseExpression(); |
| if (condExpr instanceof CanBooleanEval) { |
| CanBooleanEval condEval = (CanBooleanEval) condExpr; |
| if (condEval.isBooleanTrue()) { |
| JsBinaryOperation binOp = new JsBinaryOperation(x.getSourceInfo(), |
| JsBinaryOperator.AND, condExpr, thenExpr); |
| ctx.replaceMe(accept(binOp)); |
| } else if (condEval.isBooleanFalse()) { |
| // e.g. (false() ? then : else) -> false() || else |
| JsBinaryOperation binOp = new JsBinaryOperation(x.getSourceInfo(), |
| JsBinaryOperator.OR, condExpr, elseExpr); |
| ctx.replaceMe(accept(binOp)); |
| } |
| } |
| } |
| |
| /** |
| * Convert do { } while (false); into a block. |
| */ |
| @Override |
| public void endVisit(JsDoWhile x, JsContext ctx) { |
| evalBooleanContext.remove(x.getCondition()); |
| |
| JsExpression expr = x.getCondition(); |
| if (expr instanceof CanBooleanEval) { |
| CanBooleanEval cond = (CanBooleanEval) expr; |
| |
| // If false, replace do with do's body |
| if (cond.isBooleanFalse()) { |
| // Unless it contains break/continue statements |
| FindBreakContinueStatementsVisitor visitor = new FindBreakContinueStatementsVisitor(); |
| visitor.accept(x.getBody()); |
| if (!visitor.hasBreakContinueStatements()) { |
| JsBlock block = new JsBlock(x.getSourceInfo()); |
| block.getStatements().add(x.getBody()); |
| block.getStatements().add(expr.makeStmt()); |
| ctx.replaceMe(accept(block)); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void endVisit(JsExprStmt x, JsContext ctx) { |
| if (!x.getExpression().hasSideEffects()) { |
| if (ctx.canRemove()) { |
| ctx.removeMe(); |
| } else { |
| ctx.replaceMe(new JsEmpty(x.getSourceInfo())); |
| } |
| } |
| } |
| |
| /** |
| * Prune for (X; false(); Y) statements, make sure X and false() are run. |
| */ |
| @Override |
| public void endVisit(JsFor x, JsContext ctx) { |
| evalBooleanContext.remove(x.getCondition()); |
| |
| JsExpression expr = x.getCondition(); |
| if (expr instanceof CanBooleanEval) { |
| CanBooleanEval cond = (CanBooleanEval) expr; |
| |
| // If false, replace with initializers and condition. |
| if (cond.isBooleanFalse()) { |
| JsBlock block = new JsBlock(x.getSourceInfo()); |
| if (x.getInitExpr() != null) { |
| block.getStatements().add(x.getInitExpr().makeStmt()); |
| } |
| if (x.getInitVars() != null) { |
| block.getStatements().add(x.getInitVars()); |
| } |
| block.getStatements().add(expr.makeStmt()); |
| JsStatement decls = ensureDeclarations(x.getBody()); |
| if (decls != null) { |
| block.getStatements().add(decls); |
| } |
| ctx.replaceMe(accept(block)); |
| } |
| } |
| } |
| |
| /** |
| * Simplify if statements. |
| */ |
| @Override |
| public void endVisit(JsIf x, JsContext ctx) { |
| evalBooleanContext.remove(x.getIfExpr()); |
| |
| JsExpression condExpr = x.getIfExpr(); |
| if (condExpr instanceof CanBooleanEval) { |
| if (tryStaticEvalIf(x, (CanBooleanEval) condExpr, ctx)) { |
| return; |
| } |
| } |
| |
| JsStatement thenStmt = x.getThenStmt(); |
| JsStatement elseStmt = x.getElseStmt(); |
| boolean thenIsEmpty = JsUtils.isEmpty(thenStmt); |
| boolean elseIsEmpty = JsUtils.isEmpty(elseStmt); |
| JsExpression thenExpr = JsUtils.extractExpression(thenStmt); |
| JsExpression elseExpr = JsUtils.extractExpression(elseStmt); |
| |
| if (thenIsEmpty && elseIsEmpty) { |
| // Convert "if (a()) {}" => "a()". |
| ctx.replaceMe(condExpr.makeStmt()); |
| } else if (thenExpr != null && elseExpr != null) { |
| // Convert "if (a()) {b()} else {c()}" => "a()?b():c()". |
| JsConditional cond = new JsConditional(x.getSourceInfo(), |
| x.getIfExpr(), thenExpr, elseExpr); |
| ctx.replaceMe(accept(cond.makeStmt())); |
| } else if (thenIsEmpty && elseExpr != null) { |
| // Convert "if (a()) {} else {b()}" => a()||b(). |
| JsBinaryOperation op = new JsBinaryOperation(x.getSourceInfo(), |
| JsBinaryOperator.OR, x.getIfExpr(), elseExpr); |
| ctx.replaceMe(accept(op.makeStmt())); |
| } else if (thenIsEmpty && !elseIsEmpty) { |
| // Convert "if (a()) {} else {stuff}" => "if (!a()) {stuff}". |
| JsUnaryOperation negatedOperation = new JsPrefixOperation( |
| x.getSourceInfo(), JsUnaryOperator.NOT, x.getIfExpr()); |
| JsIf newIf = new JsIf(x.getSourceInfo(), negatedOperation, elseStmt, |
| null); |
| ctx.replaceMe(accept(newIf)); |
| } else if (elseIsEmpty && thenExpr != null) { |
| // Convert "if (a()) {b()}" => "a()&&b()". |
| JsBinaryOperation op = new JsBinaryOperation(x.getSourceInfo(), |
| JsBinaryOperator.AND, x.getIfExpr(), thenExpr); |
| ctx.replaceMe(accept(op.makeStmt())); |
| } else if (elseIsEmpty && elseStmt != null) { |
| // Convert "if (a()) {b()} else {}" => "if (a()) {b()}". |
| JsIf newIf = new JsIf(x.getSourceInfo(), x.getIfExpr(), thenStmt, null); |
| ctx.replaceMe(accept(newIf)); |
| } |
| } |
| |
| /** |
| * Change !!x to x in a boolean context. |
| */ |
| @Override |
| public void endVisit(JsPrefixOperation x, JsContext 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 ctx) { |
| evalBooleanContext.remove(x.getCondition()); |
| |
| JsExpression expr = x.getCondition(); |
| if (expr instanceof CanBooleanEval) { |
| CanBooleanEval cond = (CanBooleanEval) expr; |
| |
| // If false, replace with condition. |
| if (cond.isBooleanFalse()) { |
| JsBlock block = new JsBlock(x.getSourceInfo()); |
| block.getStatements().add(expr.makeStmt()); |
| JsStatement decls = ensureDeclarations(x.getBody()); |
| if (decls != null) { |
| block.getStatements().add(decls); |
| } |
| ctx.replaceMe(accept(block)); |
| } |
| } |
| } |
| |
| @Override |
| public boolean visit(JsConditional x, JsContext ctx) { |
| evalBooleanContext.add(x.getTestExpression()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsDoWhile x, JsContext ctx) { |
| evalBooleanContext.add(x.getCondition()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsFor x, JsContext ctx) { |
| evalBooleanContext.add(x.getCondition()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsIf x, JsContext ctx) { |
| evalBooleanContext.add(x.getIfExpr()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsPrefixOperation x, JsContext ctx) { |
| if (x.getOperator() == JsUnaryOperator.NOT) { |
| evalBooleanContext.add(x.getArg()); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsWhile x, JsContext ctx) { |
| evalBooleanContext.add(x.getCondition()); |
| return true; |
| } |
| |
| /** |
| * Given an expression, determine if the addition operator would cause a |
| * string coercion to happen. |
| */ |
| private boolean additionCoercesToString(JsExpression expr) { |
| if (expr instanceof JsStringLiteral) { |
| return true; |
| } |
| |
| /* |
| * Because the nodes passed into this method are visited on exit, it is |
| * worthwile to memoize the result for this function. |
| */ |
| Boolean toReturn = coercesToStringMap.get(expr); |
| if (toReturn != null) { |
| return toReturn; |
| } |
| toReturn = false; |
| |
| if (expr instanceof JsBinaryOperation) { |
| JsBinaryOperation op = (JsBinaryOperation) expr; |
| switch (op.getOperator()) { |
| case ADD: |
| toReturn = additionCoercesToString(op.getArg1()) |
| || additionCoercesToString(op.getArg2()); |
| break; |
| case COMMA: |
| toReturn = additionCoercesToString(op.getArg2()); |
| break; |
| } |
| |
| if (op.getOperator().isAssignment()) { |
| toReturn = additionCoercesToString(op.getArg2()); |
| } |
| } |
| |
| /* |
| * TODO: Consider adding heuristics to detect String(foo), typeof(foo), |
| * and foo.toString(). The latter is debatable, since an implementation |
| * might not actually return a string. |
| */ |
| |
| coercesToStringMap.put(expr, toReturn); |
| return toReturn; |
| } |
| |
| /** |
| * This method MUST be called whenever any statements are removed from a |
| * function. This is because some statements, such as JsVars or JsFunction |
| * have the effect of defining local variables, no matter WHERE they are in |
| * the function. The returned statement (if any), must be executed. It is |
| * also possible for stmt to be directly returned, in which case the caller |
| * should not perform AST changes that would cause an infinite optimization |
| * loop. |
| * |
| * Note: EvalFunctionsAtTopScope will have changed any JsFunction |
| * declarations into statements before this visitor runs. |
| */ |
| private JsStatement ensureDeclarations(JsStatement stmt) { |
| if (stmt == null) { |
| return null; |
| } |
| MustExecVisitor mev = new MustExecVisitor(); |
| mev.accept(stmt); |
| List<JsStatement> stmts = mev.getStatements(); |
| if (stmts.isEmpty()) { |
| return null; |
| } else if (stmts.size() == 1) { |
| return stmts.get(0); |
| } else { |
| JsBlock jsBlock = new JsBlock(stmt.getSourceInfo()); |
| jsBlock.getStatements().addAll(stmts); |
| return jsBlock; |
| } |
| } |
| |
| private boolean tryStaticEvalIf(JsIf x, CanBooleanEval cond, JsContext ctx) { |
| JsStatement thenStmt = x.getThenStmt(); |
| JsStatement elseStmt = x.getElseStmt(); |
| if (cond.isBooleanTrue()) { |
| JsBlock block = new JsBlock(x.getSourceInfo()); |
| block.getStatements().add(x.getIfExpr().makeStmt()); |
| if (thenStmt != null) { |
| block.getStatements().add(thenStmt); |
| } |
| JsStatement decls = ensureDeclarations(elseStmt); |
| if (decls != null) { |
| block.getStatements().add(decls); |
| } |
| ctx.replaceMe(accept(block)); |
| return true; |
| } else if (cond.isBooleanFalse()) { |
| JsBlock block = new JsBlock(x.getSourceInfo()); |
| block.getStatements().add(x.getIfExpr().makeStmt()); |
| if (elseStmt != null) { |
| block.getStatements().add(elseStmt); |
| } |
| JsStatement decls = ensureDeclarations(thenStmt); |
| if (decls != null) { |
| block.getStatements().add(decls); |
| } |
| ctx.replaceMe(accept(block)); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| private static final String NAME = JsStaticEval.class.getSimpleName(); |
| |
| /** |
| * A set of the JS operators that are fully associative in nature; NOT included in this set are |
| * operators that are only left-associative or right-associative or perform floating point |
| * arithmetic. |
| */ |
| private static final Set<JsBinaryOperator> REORDERABLE_OPERATORS = EnumSet.of( |
| JsBinaryOperator.OR, JsBinaryOperator.AND, JsBinaryOperator.BIT_AND, |
| JsBinaryOperator.BIT_OR, JsBinaryOperator.COMMA); |
| |
| public static OptimizerStats exec(JsProgram program) { |
| Event optimizeJsEvent = SpeedTracerLogger.start( |
| CompilerEventType.OPTIMIZE_JS, "optimizer", NAME); |
| OptimizerStats stats = new JsStaticEval(program).execImpl(); |
| optimizeJsEvent.end("didChange", "" + stats.didChange()); |
| return stats; |
| } |
| |
| /** |
| * Simplify short circuit AND expressions. |
| * |
| * <pre> |
| * if (true && isWhatever()) -> if (isWhatever()), unless side effects |
| * if (false() && isWhatever()) -> if (false()) |
| * </pre> |
| */ |
| protected static JsExpression shortCircuitAnd(JsBinaryOperation expr) { |
| JsExpression arg1 = expr.getArg1(); |
| JsExpression arg2 = expr.getArg2(); |
| if (arg1 instanceof CanBooleanEval) { |
| CanBooleanEval eval1 = (CanBooleanEval) arg1; |
| if (eval1.isBooleanTrue() && !arg1.hasSideEffects()) { |
| return arg2; |
| } else if (eval1.isBooleanFalse()) { |
| return arg1; |
| } |
| } |
| return expr; |
| } |
| |
| /** |
| * Simplify short circuit OR expressions. |
| * |
| * <pre> |
| * if (true() || isWhatever()) -> if (true()) |
| * if (false || isWhatever()) -> if (isWhatever()), unless side effects |
| * </pre> |
| */ |
| protected static JsExpression shortCircuitOr(JsBinaryOperation expr) { |
| JsExpression arg1 = expr.getArg1(); |
| JsExpression arg2 = expr.getArg2(); |
| if (arg1 instanceof CanBooleanEval) { |
| CanBooleanEval eval1 = (CanBooleanEval) arg1; |
| if (eval1.isBooleanTrue()) { |
| return arg1; |
| } else if (eval1.isBooleanFalse() && !arg1.hasSideEffects()) { |
| return arg2; |
| } |
| } |
| return expr; |
| } |
| |
| protected static JsExpression trySimplifyComma(JsBinaryOperation expr) { |
| JsExpression arg1 = expr.getArg1(); |
| JsExpression arg2 = expr.getArg2(); |
| if (!arg1.hasSideEffects()) { |
| return arg2; |
| } |
| return expr; |
| } |
| |
| private static JsExpression simplifyEqAndRefEq(JsBinaryOperation expr) { |
| JsExpression arg1 = expr.getArg1(); |
| JsExpression arg2 = expr.getArg2(); |
| |
| if (arg1 instanceof JsNullLiteral) { |
| return simplifyNullEq(expr, arg2); |
| } |
| |
| if (arg2 instanceof JsNullLiteral) { |
| return simplifyNullEq(expr, arg1); |
| } |
| |
| if (arg1 instanceof JsNumberLiteral && arg2 instanceof JsNumberLiteral) { |
| return JsBooleanLiteral.get(((JsNumberLiteral) arg1).getValue() |
| == ((JsNumberLiteral) arg2).getValue()); |
| } |
| |
| if (arg1 instanceof JsStringLiteral && arg2 instanceof JsStringLiteral) { |
| return JsBooleanLiteral.get( |
| ((JsStringLiteral) arg1).getValue().equals(((JsStringLiteral) arg2).getValue())); |
| } |
| // no simplification made |
| return expr; |
| } |
| |
| /** |
| * Simplify exp == null. |
| */ |
| private static JsExpression simplifyNullEq(JsExpression original, JsExpression exp) { |
| assert (original != null); |
| |
| if (exp instanceof JsValueLiteral) { |
| // "undefined" is not a JsValueLiteral, so the only way |
| // the result can be true is if exp is itself a JsNullLiteral |
| boolean result = exp instanceof JsNullLiteral; |
| return JsBooleanLiteral.get(result); |
| } |
| |
| // no simplification made |
| return original; |
| } |
| |
| private static JsExpression simplifyNeAndRefNe(JsBinaryOperation expr) { |
| JsExpression arg1 = expr.getArg1(); |
| JsExpression arg2 = expr.getArg2(); |
| |
| JsExpression simplifiedEq = simplifyEqAndRefEq(expr); |
| if (simplifiedEq == expr) { |
| return expr; |
| } |
| |
| assert simplifiedEq instanceof JsBooleanLiteral; |
| |
| return JsBooleanLiteral.get(!((JsBooleanLiteral) simplifiedEq).getValue()); |
| } |
| |
| /** |
| * Simplify a op b. |
| */ |
| private static JsExpression simplifyOp(JsBinaryOperation expr) { |
| SourceInfo info = expr.getSourceInfo(); |
| JsExpression arg1 = expr.getArg1(); |
| JsExpression arg2 = expr.getArg2(); |
| JsBinaryOperator op = expr.getOperator(); |
| |
| if (op == JsBinaryOperator.ADD && |
| (arg1 instanceof JsStringLiteral || arg2 instanceof JsStringLiteral)) { |
| // cases: number + string or string + number |
| StringBuilder result = new StringBuilder(); |
| if (appendLiteral(result, (JsValueLiteral) arg1) |
| && appendLiteral(result, (JsValueLiteral) arg2)) { |
| return new JsStringLiteral(info, result.toString()); |
| } |
| return expr; |
| } |
| |
| if (arg1 instanceof JsNumberLiteral && arg2 instanceof JsNumberLiteral) { |
| double num1 = ((JsNumberLiteral) arg1).getValue(); |
| double num2 = ((JsNumberLiteral) arg2).getValue(); |
| Object result; |
| |
| switch (op) { |
| case ADD: |
| result = Ieee754_64_Arithmetic.add(num1, num2); |
| break; |
| case SUB: |
| result = Ieee754_64_Arithmetic.subtract(num1, num2); |
| break; |
| case MUL: |
| result = Ieee754_64_Arithmetic.multiply(num1, num2); |
| break; |
| case DIV: |
| result = Ieee754_64_Arithmetic.divide(num1, num2); |
| break; |
| case MOD: |
| result = Ieee754_64_Arithmetic.mod(num1, num2); |
| break; |
| case LT: |
| result = Ieee754_64_Arithmetic.lt(num1, num2); |
| break; |
| case LTE: |
| result = Ieee754_64_Arithmetic.le(num1, num2); |
| break; |
| case GT: |
| result = Ieee754_64_Arithmetic.gt(num1, num2); |
| break; |
| case GTE: |
| result = Ieee754_64_Arithmetic.ge(num1, num2); |
| break; |
| default: |
| throw new InternalCompilerException("Can't handle simplify of op " + op); |
| } |
| return result instanceof Double ? |
| new JsNumberLiteral(info, ((Double) result).doubleValue()) : |
| JsBooleanLiteral.get(((Boolean) result).booleanValue()); |
| } |
| return expr; |
| } |
| |
| private static boolean appendLiteral(StringBuilder result, JsValueLiteral val) { |
| if (val instanceof JsNumberLiteral) { |
| double number = ((JsNumberLiteral) val).getValue(); |
| result.append(ScriptRuntime.numberToString(number, 10)); |
| } else if (val instanceof JsStringLiteral) { |
| result.append(((JsStringLiteral) val).getValue()); |
| } else if (val instanceof JsBooleanLiteral) { |
| result.append(((JsBooleanLiteral) val).getValue()); |
| } else if (val instanceof JsNullLiteral) { |
| result.append("null"); |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Makes expressions as expressions as left-normal as possible, i.e. prefers |
| * |
| * o1 o1 |
| * / \ / \ |
| * o2 e3 to e1 o2 |
| * / \ / \ |
| * e1 e2 e2 e3 |
| * |
| * when equivalent. |
| */ |
| private static JsExpression maybeReorderOperations(JsExpression x) { |
| if (!(x instanceof JsBinaryOperation)) { |
| return x; |
| } |
| |
| JsBinaryOperation expr = (JsBinaryOperation) x; |
| JsBinaryOperator outerOp = expr.getOperator(); |
| |
| if (!REORDERABLE_OPERATORS.contains(outerOp)) { |
| return expr; |
| } |
| |
| if (!(expr.getArg2() instanceof JsBinaryOperation) || |
| ((JsBinaryOperation) expr.getArg2()).getOperator() != outerOp) { |
| return expr; |
| } |
| |
| JsBinaryOperation leftExpr = (JsBinaryOperation) expr.getArg2(); |
| |
| // Perform rotation. |
| return new JsBinaryOperation(x.getSourceInfo(), leftExpr.getOperator(), |
| maybeReorderOperations( |
| new JsBinaryOperation(x.getSourceInfo(), outerOp, expr.getArg1(), leftExpr.getArg1())), |
| leftExpr.getArg2()); |
| } |
| |
| private final JsProgram program; |
| |
| public JsStaticEval(JsProgram program) { |
| this.program = program; |
| } |
| |
| public OptimizerStats execImpl() { |
| StaticEvalVisitor sev = new StaticEvalVisitor(); |
| sev.accept(program); |
| OptimizerStats stats = new OptimizerStats(NAME); |
| if (sev.didChange()) { |
| stats.recordModified(); |
| } |
| return stats; |
| } |
| } |