Skip to content

Commit

Permalink
Prune if and while statements when the expression has a statically kn…
Browse files Browse the repository at this point in the history
…own value but has side effects.

Preserve volatile field access when pruning.
	Change on 2015/10/30 by kstanger <kstanger@google.com>
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=106699067
  • Loading branch information
kstanger authored and tomball committed Nov 11, 2015
1 parent c39fba3 commit bb7e9e9
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 33 deletions.
Expand Up @@ -21,10 +21,12 @@
import com.google.devtools.j2objc.ast.BooleanLiteral; import com.google.devtools.j2objc.ast.BooleanLiteral;
import com.google.devtools.j2objc.ast.DoStatement; import com.google.devtools.j2objc.ast.DoStatement;
import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.ExpressionStatement;
import com.google.devtools.j2objc.ast.IfStatement; import com.google.devtools.j2objc.ast.IfStatement;
import com.google.devtools.j2objc.ast.InfixExpression; import com.google.devtools.j2objc.ast.InfixExpression;
import com.google.devtools.j2objc.ast.ParenthesizedExpression; import com.google.devtools.j2objc.ast.ParenthesizedExpression;
import com.google.devtools.j2objc.ast.PrefixExpression; import com.google.devtools.j2objc.ast.PrefixExpression;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TreeVisitor; import com.google.devtools.j2objc.ast.TreeVisitor;
import com.google.devtools.j2objc.ast.WhileStatement; import com.google.devtools.j2objc.ast.WhileStatement;
import com.google.devtools.j2objc.util.TranslationUtil; import com.google.devtools.j2objc.util.TranslationUtil;
Expand All @@ -42,12 +44,17 @@ public class ConstantBranchPruner extends TreeVisitor {


@Override @Override
public void endVisit(IfStatement node) { public void endVisit(IfStatement node) {
Boolean value = getValue(node.getExpression()); Expression expr = node.getExpression();
Boolean value = getKnownValue(expr);
if (value != null) { if (value != null) {
Expression sideEffects = extractSideEffects(expr);
if (sideEffects != null) {
TreeUtil.insertBefore(node, new ExpressionStatement(sideEffects));
}
if (value) { if (value) {
node.replaceWith(node.getThenStatement().copy()); node.replaceWith(TreeUtil.remove(node.getThenStatement()));
} else if (node.getElseStatement() != null) { } else if (node.getElseStatement() != null) {
node.replaceWith(node.getElseStatement().copy()); node.replaceWith(TreeUtil.remove(node.getElseStatement()));
} else { } else {
node.remove(); node.remove();
} }
Expand All @@ -61,25 +68,26 @@ public void endVisit(InfixExpression node) {
return; return;
} }
List<Expression> operands = node.getOperands(); List<Expression> operands = node.getOperands();
boolean foundSideEffect = false; int lastSideEffect = -1;
for (Iterator<Expression> it = operands.iterator(); it.hasNext(); ) { for (int i = 0; i < operands.size(); i++) {
Expression expr = it.next(); Expression expr = operands.get(i);
foundSideEffect |= TranslationUtil.hasSideEffect(expr); if (TranslationUtil.hasSideEffect(expr)) {
Boolean constantVal = getValue(expr); lastSideEffect = i;
if (constantVal == null) { }
Boolean knownVal = getKnownValue(expr);
if (knownVal == null) {
continue; continue;
} }
if (constantVal == (operator == CONDITIONAL_OR)) { if (knownVal == (operator == CONDITIONAL_OR)) {
// Whole expression evaluates to 'constantVal'. // Whole expression evaluates to 'knownVal'.
if (!foundSideEffect) { operands.subList(lastSideEffect + 1, operands.size()).clear();
// We can safely prune the whole infix if the precededing expressions if (lastSideEffect < i) {
// have no side effects. operands.add(new BooleanLiteral(knownVal, typeEnv));
node.replaceWith(new BooleanLiteral(constantVal, typeEnv));
return;
} }
} else { break;
} else if (lastSideEffect < i) {
// Else remove unnecessary constant value. // Else remove unnecessary constant value.
it.remove(); operands.remove(i--);
} }
} }


Expand All @@ -102,7 +110,7 @@ public void endVisit(InfixExpression node) {
*/ */
@Override @Override
public void endVisit(PrefixExpression node) { public void endVisit(PrefixExpression node) {
Boolean value = getValue(node.getOperand()); Boolean value = getReplaceableValue(node.getOperand());
if (node.getOperator() == PrefixExpression.Operator.NOT && value != null) { if (node.getOperator() == PrefixExpression.Operator.NOT && value != null) {
node.replaceWith(new BooleanLiteral(!value, typeEnv)); node.replaceWith(new BooleanLiteral(!value, typeEnv));
} }
Expand All @@ -113,30 +121,97 @@ public void endVisit(PrefixExpression node) {
*/ */
@Override @Override
public void endVisit(ParenthesizedExpression node) { public void endVisit(ParenthesizedExpression node) {
if (getValue(node.getExpression()) != null) { if (getReplaceableValue(node.getExpression()) != null) {
node.replaceWith(node.getExpression().copy()); node.replaceWith(node.getExpression().copy());
} }
} }


@Override @Override
public void endVisit(WhileStatement node) { public void endVisit(WhileStatement node) {
if (getValue(node.getExpression()) == FALSE) { Expression expr = node.getExpression();
node.remove(); if (getKnownValue(expr) == FALSE) {
Expression sideEffects = extractSideEffects(expr);
if (sideEffects != null) {
node.replaceWith(new ExpressionStatement(sideEffects));
} else {
node.remove();
}
} }
} }


/** /**
* Returns TRUE or FALSE if expression is a boolean constant, else * Returns TRUE or FALSE if expression is a boolean constant and has no side
* null (unknown statically). * effects, else null (unknown statically).
*/
private Boolean getReplaceableValue(Expression expr) {
return TranslationUtil.hasSideEffect(expr) ? null : getKnownValue(expr);
}

/**
* Returns TRUE of FALSE if 'expr' is a boolean expression and its value is
* known statically. The caller should be careful when replacing this
* expression as it may have side effects.
*/ */
private Boolean getValue(Expression expr) { private Boolean getKnownValue(Expression expr) {
Object value = expr.getConstantValue(); Object value = expr.getConstantValue();
if (value instanceof Boolean) { if (value instanceof Boolean) {
return (Boolean) value; return (Boolean) value;
} }
if (expr instanceof BooleanLiteral) { switch (expr.getKind()) {
return ((BooleanLiteral) expr).booleanValue(); case BOOLEAN_LITERAL:
return ((BooleanLiteral) expr).booleanValue();
case INFIX_EXPRESSION:
{
InfixExpression infixExpr = (InfixExpression) expr;
InfixExpression.Operator op = infixExpr.getOperator();
if (op == CONDITIONAL_AND || op == CONDITIONAL_OR) {
// We assume that this node has already been visited and pruned so
// if it has a known value, it will be equal to the last operand.
List<Expression> operands = infixExpr.getOperands();
Boolean lastOperand = getKnownValue(operands.get(operands.size() - 1));
if (lastOperand != null && lastOperand.booleanValue() == (op == CONDITIONAL_OR)) {
return lastOperand;
}
}
return null;
}
case PARENTHESIZED_EXPRESSION:
return getKnownValue(((ParenthesizedExpression) expr).getExpression());
default:
return null;
}
}

/**
* Returns an expression containing the side effects of the given expression.
* The evaluated result of the expression may differ from the original.
*/
private Expression extractSideEffects(Expression expr) {
switch (expr.getKind()) {
case INFIX_EXPRESSION:
{
List<Expression> operands = ((InfixExpression) expr).getOperands();
Expression lastOperand = operands.remove(operands.size() - 1);
lastOperand = extractSideEffects(lastOperand);
if (lastOperand != null) {
operands.add(lastOperand);
}
if (operands.size() == 1) {
return operands.remove(0);
}
return TreeUtil.remove(expr);
}
case PARENTHESIZED_EXPRESSION:
{
Expression sideEffects = extractSideEffects(
((ParenthesizedExpression) expr).getExpression());
if (sideEffects != null) {
return ParenthesizedExpression.parenthesize(sideEffects);
}
return null;
}
default:
return null;
} }
return null;
} }
} }
Expand Up @@ -189,6 +189,10 @@ public static boolean isAssigned(Expression node) {
* would be unsafe to prune the given node from the tree. * would be unsafe to prune the given node from the tree.
*/ */
public static boolean hasSideEffect(Expression expr) { public static boolean hasSideEffect(Expression expr) {
IVariableBinding var = TreeUtil.getVariableBinding(expr);
if (var != null && BindingUtil.isVolatile(var)) {
return true;
}
switch (expr.getKind()) { switch (expr.getKind()) {
case BOOLEAN_LITERAL: case BOOLEAN_LITERAL:
case CHARACTER_LITERAL: case CHARACTER_LITERAL:
Expand Down
Expand Up @@ -151,20 +151,38 @@ public void testOr() throws IOException {
// Verify method invocation paired with constant is not pruned because methods // Verify method invocation paired with constant is not pruned because methods
// may have side effects. // may have side effects.
public void testMethodAnd() throws IOException { public void testMethodAnd() throws IOException {
// TODO(kstanger): Find a way to prune the unreachable branches while
// preserving the portions of the expression that have side effects.
String translation = translateSourceFile( String translation = translateSourceFile(
"class Test { " "class Test { "
+ "static final boolean DEBUG = true; " + "static final boolean DEBUG = true; "
+ "static final boolean NDEBUG = false;" + "static final boolean NDEBUG = false;"
+ "boolean b;"
+ "boolean enabled() { return true; }" + "boolean enabled() { return true; }"
+ "int test() { int result; " + "int test() { int result; "
+ " if (enabled() && NDEBUG) { result = 1; } else { result = 2; }" // The "b" operand should be pruned since it has no side effect.
+ " if (enabled() && b && NDEBUG) { result = 1; } else { result = 2; }"
+ " if (enabled() || DEBUG) { result = 3; } else { result = 4; }" + " if (enabled() || DEBUG) { result = 3; } else { result = 4; }"
+ " return result; }}", + " return result; }}",
"Test", "Test.m"); "Test", "Test.m");
assertTranslation(translation, "if ([self enabled] && Test_NDEBUG)"); assertTranslatedLines(translation,
assertTranslation(translation, "if ([self enabled] || Test_DEBUG)"); "[self enabled];",
"{",
" result = 2;",
"}",
"[self enabled];",
"{",
" result = 3;",
"}");
}

public void testWhileStatementPrunedWithSideEffects() throws IOException {
String translation = translateSourceFile(
"class Test { boolean getB() { return true; } int test(boolean b) { "
+ "while (b && (getB() && false)) { return 1; } return 0; } }", "Test", "Test.m");
assertTranslatedLines(translation,
"- (jint)testWithBoolean:(jboolean)b {",
" b && ([self getB]);",
" return 0;",
"}");
} }


public void testExpressionPruning() throws IOException { public void testExpressionPruning() throws IOException {
Expand All @@ -181,4 +199,13 @@ public void testExpressionPruning() throws IOException {
assertTranslatedLines(translation, assertTranslatedLines(translation,
"- (jboolean)test {", "if (A_nonConstant_) return false;", "return true;", "}"); "- (jboolean)test {", "if (A_nonConstant_) return false;", "return true;", "}");
} }

// Verify that volatile loads aren't pruned because they provide a memory
// barrier.
public void testVolatileLoad() throws IOException {
String translation = translateSourceFile(
"class Test { volatile int i; boolean test() { return i == 1 && false; } }",
"Test", "Test.m");
assertTranslation(translation, "return JreLoadVolatileInt(&i_) == 1 && false;");
}
} }

0 comments on commit bb7e9e9

Please sign in to comment.