Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 mustExec = new ArrayList();
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 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 evalBooleanContext = new HashSet();
/**
* This is used by {@link #additionCoercesToString}.
*/
private Map coercesToStringMap = new IdentityHashMap();
@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 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 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 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.
*
*
* if (true && isWhatever()) -> if (isWhatever()), unless side effects
* if (false() && isWhatever()) -> if (false())
*