Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 129 additions & 39 deletions dev/core/src/com/google/gwt/dev/js/JsStaticEval.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@
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.thirdparty.guava.common.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -104,7 +105,7 @@ private static class MustExecVisitor extends JsVisitor {

private final List<JsStatement> mustExec = new ArrayList<JsStatement>();

public MustExecVisitor() {
MustExecVisitor() {
}

@Override
Expand Down Expand Up @@ -133,7 +134,7 @@ public void endVisit(JsVars x, JsContext ctx) {
}
}

public List<JsStatement> getStatements() {
private List<JsStatement> getStatements() {
return mustExec;
}

Expand All @@ -144,21 +145,55 @@ public boolean visit(JsFunction x, JsContext ctx) {
}
}

/**
* Describes how is the result of evaluation used.
*/
private enum EvalMode {
BOOL, // result will be coerced to a boolean
VOID // result will be discarded
}

/**
* Does static evals.
*
* TODO: borrow more concepts from
* {@link com.google.gwt.dev.jjs.impl.DeadCodeElimination}, such as ignored
* expression results.
Comment thread
zbynek marked this conversation as resolved.
*/
private class StaticEvalVisitor extends JsModVisitor {
@VisibleForTesting
static class StaticEvalVisitor extends JsModVisitor {

private Set<JsExpression> evalBooleanContext = new HashSet<JsExpression>();
/**
* Stores how are expression evaluations used.
* Missing entry = no coercion, difference between null and false matters.
*/
private final Map<JsExpression, EvalMode> evalContext = new HashMap<>();

/**
* This is used by {@link #additionCoercesToString}.
*/
private Map<JsExpression, Boolean> coercesToStringMap = new IdentityHashMap<JsExpression, Boolean>();
private Map<JsExpression, Boolean> coercesToStringMap = new IdentityHashMap<>();
Comment thread
zbynek marked this conversation as resolved.
Outdated

@Override
public boolean visit(JsExprStmt x, JsContext ctx) {
evalContext.put(x.getExpression(), EvalMode.VOID);
return true;
}

@Override
public boolean visit(JsBinaryOperation x, JsContext ctx) {
if (evalContext.containsKey(x)
&& (x.getOperator() == JsBinaryOperator.AND || x.getOperator() == JsBinaryOperator.OR)) {
evalContext.put(x.getArg1(), EvalMode.BOOL);
evalContext.put(x.getArg2(), evalContext.get(x));
} else if (x.getOperator() == JsBinaryOperator.COMMA) {
evalContext.put(x.getArg1(), EvalMode.VOID);
Comment thread
zbynek marked this conversation as resolved.
if (evalContext.containsKey(x)) {
evalContext.put(x.getArg2(), evalContext.get(x));
}
}
return true;
}

@Override
public void endVisit(JsBinaryOperation x, JsContext ctx) {
Expand All @@ -167,11 +202,13 @@ public void endVisit(JsBinaryOperation x, JsContext ctx) {
JsExpression arg2 = x.getArg2();

JsExpression result = x;

if (op == JsBinaryOperator.AND) {
result = shortCircuitAnd(x);
if (evalContext.get(x) == EvalMode.VOID
&& !arg2.hasSideEffects() && !op.isAssignment()) {
result = arg1;
} else if (op == JsBinaryOperator.AND) {
result = shortCircuitAnd(x, evalContext);
} else if (op == JsBinaryOperator.OR) {
result = shortCircuitOr(x);
result = shortCircuitOr(x, evalContext);
} else if (op == JsBinaryOperator.COMMA) {
result = trySimplifyComma(x);
} else if (op == JsBinaryOperator.EQ || op == JsBinaryOperator.REF_EQ) {
Expand All @@ -195,7 +232,8 @@ public void endVisit(JsBinaryOperation x, JsContext ctx) {
break;
}
}

evalContext.remove(arg1);
evalContext.remove(arg2);
result = maybeReorderOperations(result);

if (result != x) {
Expand Down Expand Up @@ -256,21 +294,29 @@ public void endVisit(JsBlock x, JsContext ctx) {

@Override
public void endVisit(JsConditional x, JsContext ctx) {
evalBooleanContext.remove(x.getTestExpression());
evalContext.remove(x.getTestExpression());
evalContext.remove(x.getThenExpression());
evalContext.remove(x.getElseExpression());

JsExpression condExpr = x.getTestExpression();
JsExpression thenExpr = x.getThenExpression();
JsExpression elseExpr = x.getElseExpression();
if (condExpr instanceof CanBooleanEval) {
if (condExpr instanceof JsBinaryOperation
&& ((JsBinaryOperation) condExpr).getOperator() == JsBinaryOperator.COMMA) {
JsBinaryOperation condition = (JsBinaryOperation) condExpr;
JsExpression newConditional = accept(new JsConditional(x.getSourceInfo(),
condition.getArg2(), thenExpr, elseExpr));
ctx.replaceMe(withSideEffect(newConditional, condition.getArg1(), x.getSourceInfo()));
} else if (condExpr instanceof CanBooleanEval) {
CanBooleanEval condEval = (CanBooleanEval) condExpr;
if (condEval.isBooleanTrue()) {
JsBinaryOperation binOp = new JsBinaryOperation(x.getSourceInfo(),
JsBinaryOperator.AND, condExpr, thenExpr);
JsBinaryOperator.COMMA, condExpr, thenExpr);
ctx.replaceMe(accept(binOp));
} else if (condEval.isBooleanFalse()) {
// e.g. (false() ? then : else) -> false() || else
// e.g. (false() ? then : else) -> (false() , else)
JsBinaryOperation binOp = new JsBinaryOperation(x.getSourceInfo(),
JsBinaryOperator.OR, condExpr, elseExpr);
JsBinaryOperator.COMMA, condExpr, elseExpr);
ctx.replaceMe(accept(binOp));
}
}
Expand All @@ -281,7 +327,7 @@ public void endVisit(JsConditional x, JsContext ctx) {
*/
@Override
public void endVisit(JsDoWhile x, JsContext ctx) {
evalBooleanContext.remove(x.getCondition());
evalContext.remove(x.getCondition());

JsExpression expr = x.getCondition();
if (expr instanceof CanBooleanEval) {
Expand All @@ -304,6 +350,7 @@ public void endVisit(JsDoWhile x, JsContext ctx) {

@Override
public void endVisit(JsExprStmt x, JsContext ctx) {
evalContext.remove(x.getExpression());
if (!x.getExpression().hasSideEffects()) {
if (ctx.canRemove()) {
ctx.removeMe();
Expand All @@ -318,7 +365,7 @@ public void endVisit(JsExprStmt x, JsContext ctx) {
*/
@Override
public void endVisit(JsFor x, JsContext ctx) {
evalBooleanContext.remove(x.getCondition());
evalContext.remove(x.getCondition());

JsExpression expr = x.getCondition();
if (expr instanceof CanBooleanEval) {
Expand Down Expand Up @@ -348,7 +395,7 @@ public void endVisit(JsFor x, JsContext ctx) {
*/
@Override
public void endVisit(JsIf x, JsContext ctx) {
evalBooleanContext.remove(x.getIfExpr());
evalContext.remove(x.getIfExpr());

JsExpression condExpr = x.getIfExpr();
if (condExpr instanceof CanBooleanEval) {
Expand Down Expand Up @@ -402,10 +449,10 @@ public void endVisit(JsIf x, JsContext ctx) {
@Override
public void endVisit(JsPrefixOperation x, JsContext ctx) {
if (x.getOperator() == JsUnaryOperator.NOT) {
evalBooleanContext.remove(x.getArg());
evalContext.remove(x.getArg());
}

if (evalBooleanContext.contains(x)) {
if (evalContext.containsKey(x)) {
if ((x.getOperator() == JsUnaryOperator.NOT)
&& (x.getArg() instanceof JsPrefixOperation)) {
JsPrefixOperation arg = (JsPrefixOperation) x.getArg();
Expand All @@ -422,7 +469,7 @@ public void endVisit(JsPrefixOperation x, JsContext ctx) {
*/
@Override
public void endVisit(JsWhile x, JsContext ctx) {
evalBooleanContext.remove(x.getCondition());
evalContext.remove(x.getCondition());

JsExpression expr = x.getCondition();
if (expr instanceof CanBooleanEval) {
Expand All @@ -443,39 +490,44 @@ public void endVisit(JsWhile x, JsContext ctx) {

@Override
public boolean visit(JsConditional x, JsContext ctx) {
evalBooleanContext.add(x.getTestExpression());
evalContext.put(x.getTestExpression(), EvalMode.BOOL);
EvalMode evalMode = evalContext.get(x);
if (evalMode != null) {
evalContext.put(x.getThenExpression(), evalMode);
evalContext.put(x.getElseExpression(), evalMode);
}
return true;
}

@Override
public boolean visit(JsDoWhile x, JsContext ctx) {
evalBooleanContext.add(x.getCondition());
evalContext.put(x.getCondition(), EvalMode.BOOL);
return true;
}

@Override
public boolean visit(JsFor x, JsContext ctx) {
evalBooleanContext.add(x.getCondition());
evalContext.put(x.getCondition(), EvalMode.BOOL);
return true;
}

@Override
public boolean visit(JsIf x, JsContext ctx) {
evalBooleanContext.add(x.getIfExpr());
evalContext.put(x.getIfExpr(), EvalMode.BOOL);
return true;
}

@Override
public boolean visit(JsPrefixOperation x, JsContext ctx) {
if (x.getOperator() == JsUnaryOperator.NOT) {
evalBooleanContext.add(x.getArg());
evalContext.put(x.getArg(), EvalMode.BOOL);
}
return true;
}

@Override
public boolean visit(JsWhile x, JsContext ctx) {
evalBooleanContext.add(x.getCondition());
evalContext.put(x.getCondition(), EvalMode.BOOL);
return true;
}

Expand Down Expand Up @@ -607,41 +659,79 @@ public static int exec(JsProgram program) {
* Simplify short circuit AND expressions.
*
* <pre>
* if (true && isWhatever()) -> if (isWhatever()), unless side effects
* if (false() && isWhatever()) -> if (false())
* return true() && isWhatever() -> return isWhatever(), unless true() has side effects
* return false() && isWhatever() -> return false()
* </pre>
* In boolean context also
Comment thread
zbynek marked this conversation as resolved.
Outdated
* <pre>
* if (isWhatever() && true()) -> if (isWhatever()) unless true() has side effects
* if (isWhatever() && false()) -> if (false()) unless isWhatever() side effects
* </pre>
*/
protected static JsExpression shortCircuitAnd(JsBinaryOperation expr) {
protected static JsExpression shortCircuitAnd(JsBinaryOperation expr,
Map<JsExpression, EvalMode> evalContext) {
JsExpression arg1 = expr.getArg1();
JsExpression arg2 = expr.getArg2();
if (arg1 instanceof CanBooleanEval) {
CanBooleanEval eval1 = (CanBooleanEval) arg1;
if (eval1.isBooleanTrue() && !arg1.hasSideEffects()) {
return arg2;
if (eval1.isBooleanTrue()) {
return withSideEffect(arg2, arg1, expr.getSourceInfo());
} else if (eval1.isBooleanFalse()) {
return arg1;
}
}
// technically this operation works in VOID mode too,
// but void expressions are optimized early in endVisit
if (arg2 instanceof CanBooleanEval && evalContext.get(expr) == EvalMode.BOOL) {
CanBooleanEval eval2 = (CanBooleanEval) arg2;
if (eval2.isBooleanTrue() && !arg2.hasSideEffects()) {
return arg1;
} else if (eval2.isBooleanFalse()) {
return withSideEffect(arg2, arg1, expr.getSourceInfo());
}
}
return expr;
}

private static JsExpression withSideEffect(JsExpression main, JsExpression sideEffect,
Comment thread
zbynek marked this conversation as resolved.
Outdated
SourceInfo sourceInfo) {
return !sideEffect.hasSideEffects() ? main
: new JsBinaryOperation(sourceInfo, JsBinaryOperator.COMMA, sideEffect, main);
}

/**
* Simplify short circuit OR expressions.
*
* <pre>
* if (true() || isWhatever()) -> if (true())
* if (false || isWhatever()) -> if (isWhatever()), unless side effects
* return false() || isWhatever() -> return isWhatever(), unless false() has side effects
* return true() && isWhatever() -> return true()
* </pre>
* In boolean context also
* <pre>
* if (isWhatever() || false()) -> if (isWhatever()) unless false() has side effects
* if (isWhatever() || true()) -> if (true()) unless isWhatever() side effects
* </pre>
*/
protected static JsExpression shortCircuitOr(JsBinaryOperation expr) {
protected static JsExpression shortCircuitOr(JsBinaryOperation expr,
Map<JsExpression, EvalMode> evalContext) {
JsExpression arg1 = expr.getArg1();
JsExpression arg2 = expr.getArg2();
if (arg1 instanceof CanBooleanEval) {
CanBooleanEval eval1 = (CanBooleanEval) arg1;
if (eval1.isBooleanTrue()) {
if (eval1.isBooleanFalse()) {
return withSideEffect(arg2, arg1, expr.getSourceInfo());
} else if (eval1.isBooleanTrue()) {
return arg1;
}
}
// technically this operation works in VOID mode too,
// but void expressions are optimized early in endVisit
if (arg2 instanceof CanBooleanEval && evalContext.get(expr) == EvalMode.BOOL) {
CanBooleanEval eval2 = (CanBooleanEval) arg2;
if (eval2.isBooleanFalse() && !arg2.hasSideEffects()) {
return arg1;
} else if (eval1.isBooleanFalse() && !arg1.hasSideEffects()) {
return arg2;
} else if (eval2.isBooleanTrue()) {
return withSideEffect(arg2, arg1, expr.getSourceInfo());
}
}
return expr;
Expand Down
Loading
Loading