KT-12275 Add JS optimization that transforms the following code

```
do {
    X
    if (B) break;
} while (A)
```

to

```
do {
    X
} while (!B && A)
```

Add inversion that takes boolean expression and applies negation to it, simplifying if possible (like !!a => a).
This commit is contained in:
Alexey Andreev
2016-05-16 13:08:44 +03:00
parent b3d29adad9
commit 05dd039151
27 changed files with 812 additions and 1 deletions
@@ -22,6 +22,7 @@ class FunctionPostProcessor(root: JsFunction) {
val optimizations = listOf(
{ TemporaryAssignmentElimination(root.body).apply() },
{ RedundantLabelRemoval(root.body).apply() },
{ WhileConditionFolding(root.body).apply() },
{ DoWhileGuardElimination(root.body).apply() },
{ TemporaryVariableElimination(root).apply() },
{ RedundantBindElimination(root.body).apply() },
@@ -0,0 +1,251 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.js.inline.clean
import com.google.dart.compiler.backend.js.ast.*
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class WhileConditionFolding(val body: JsBlock) {
private var changed = false
fun apply(): Boolean {
body.accept(object : RecursiveJsVisitor() {
override fun visitLabel(x: JsLabel) {
val innerStatement = x.statement
when (innerStatement) {
is JsWhile -> process(innerStatement, x.name)
is JsDoWhile -> process(innerStatement, x.name)
else -> super.visitLabel(x)
}
}
override fun visitWhile(x: JsWhile) {
process(x, null)
}
override fun visitDoWhile(x: JsDoWhile) {
process(x, null)
}
private fun process(statement: JsWhile, name: JsName?) {
process(statement, name, { first(it) }, { removeFirst(it) }, { a, b -> JsAstUtils.and(a, b) })
}
private fun process(statement: JsDoWhile, name: JsName?) {
if (!hasContinue(statement.body, name)) {
process(statement, name, { last(it) }, { removeLast(it) }, { a, b -> JsAstUtils.and(b, a) })
}
}
private fun process(
statement: JsWhile,
name: JsName?,
find: (JsStatement) -> JsStatement,
remove: (JsStatement) -> JsStatement,
combine: (JsExpression, JsExpression) -> JsExpression
) {
statement.body.accept(this)
do {
var optimized = false
val first = find(statement.body)
val condition = extractCondition(first, name)
if (condition != null) {
statement.body = remove(statement.body)
val existingCondition = statement.condition
statement.condition = when (existingCondition) {
JsLiteral.TRUE -> condition
else -> combine(existingCondition, condition)
}
changed = true
optimized = true
}
} while (optimized)
}
/**
* Gets condition from while or do..while body and convert it into while/do..while condition
*
* For example,
*
* while (true) {
* if (!A) break;
* B()
* }
*
* This function should return `A`, (negated condition of if statement).
*/
private fun extractCondition(statement: JsStatement, label: JsName?): JsExpression? = when (statement) {
// Code like this
//
// while (A) {
// break;
// B();
// }
//
// can be rewritten as
//
// while (A && false) {
// B();
// }
//
// therefore for single `break` we should return `false`.
is JsBreak -> {
val target = statement.label?.name
if (label == null || label == target) JsLiteral.FALSE else null
}
// Code like this
//
// while (A) {
// if (!B)
// X;
// D();
// }
//
// where X is a statement, and we can extract condition `C` from it, can be rewritten as
//
// while (A && (B || C)) {
// D()
// }
// therefore we return B || C
//
// an example is
//
// while (A) {
// if (!B)
// if (!C)
// break;
// D()
// }
//
// applying this rule repeatedly we get while (A && (B || C)), which is correct
is JsIf -> {
val then = statement.thenStatement!!
if (statement.elseStatement == null) {
val nextCondition = extractCondition(then, label)
val result: JsExpression? = when (nextCondition) {
// Just a little optimization. When inner statement is a single `break`, `nextCondition` would be false.
// However, `A || false` can be rewritten as simply `A`
JsLiteral.FALSE -> JsAstUtils.negatedOptimized(statement.ifExpression)
null -> null
else -> JsAstUtils.or(JsAstUtils.negatedOptimized(statement.ifExpression), nextCondition)
}
result
}
else {
null
}
}
// A single block containing a statement with extractable condition
is JsBlock -> {
if (statement.statements.size == 1) extractCondition(statement.statements[0], label) else null
}
else -> null
}
private fun first(statement: JsStatement) = when (statement) {
is JsBlock -> statement.statements.firstOrNull() ?: statement
else -> statement
}
private fun removeFirst(statement: JsStatement) = when (statement) {
is JsBlock -> {
val statements = statement.statements
if (statements.isNotEmpty()) {
statements.removeAt(0)
}
statement
}
else -> JsBlock()
}
private fun last(statement: JsStatement) = when (statement) {
is JsBlock -> statement.statements.lastOrNull() ?: statement
else -> statement
}
private fun removeLast(statement: JsStatement) = when (statement) {
is JsBlock -> {
val statements = statement.statements
if (statements.isNotEmpty()) {
statements.removeAt(statements.lastIndex)
}
statement
}
else -> JsBlock()
}
override fun visitFunction(x: JsFunction) { }
})
return changed
}
private fun hasContinue(statement: JsStatement, label: JsName?): Boolean {
var found = false
statement.accept(object : RecursiveJsVisitor() {
private var level = 0
override fun visitContinue(x: JsContinue) {
val name = x.label?.name
if (name == null) {
if (level == 0) {
found = true
}
}
else if (name == label) {
found = true
}
}
override fun visitFor(x: JsFor) {
level++
super.visitFor(x)
level--
}
override fun visitWhile(x: JsWhile) {
level++
super.visitWhile(x)
level--
}
override fun visitDoWhile(x: JsDoWhile) {
level++
super.visitDoWhile(x)
level--
}
override fun visitForIn(x: JsForIn) {
level++
super.visitForIn(x)
level--
}
override fun visitFunction(x: JsFunction) { }
override fun visitElement(node: JsNode) {
if (!found) {
super.visitElement(node)
}
}
})
return found
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* 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 org.jetbrains.kotlin.js.test.optimizer
import org.junit.Test
class WhileConditionFoldingTest : BasicOptimizerTest("while-condition-folding") {
@Test fun simpleWhile() = box()
@Test fun simpleDoWhile() = box()
@Test fun consequentConditions() = box()
@Test fun nestedConditions() = box()
@Test fun doWhileWithContinue() = box()
@Test fun whileEvaluationOrder() = box()
@Test fun doWhileEvaluationOrder() = box()
@Test fun inNestedLoop() = box()
@Test fun inLabeledBlock() = box()
@Test fun doWhileWithNestedContinue() = box()
@Test fun labeledContinueInNestedLoop() = box()
}
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
import org.jetbrains.kotlin.js.translate.general.Translation;
import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.TopLevelFIF;
import org.jetbrains.kotlin.js.translate.utils.BindingUtils;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.name.Name;
@@ -238,6 +238,43 @@ public final class JsAstUtils {
return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
}
@NotNull
public static JsExpression negatedOptimized(@NotNull JsExpression expression) {
if (expression instanceof JsUnaryOperation) {
JsUnaryOperation unary = (JsUnaryOperation) expression;
if (unary.getOperator() == JsUnaryOperator.NOT) return unary.getArg();
}
else if (expression instanceof JsBinaryOperation) {
JsBinaryOperation binary = (JsBinaryOperation) expression;
switch (binary.getOperator()) {
case AND:
return or(negatedOptimized(binary.getArg1()), negatedOptimized(binary.getArg2()));
case OR:
return and(negatedOptimized(binary.getArg1()), negatedOptimized(binary.getArg2()));
case EQ:
return new JsBinaryOperation(JsBinaryOperator.NEQ, binary.getArg1(), binary.getArg2());
case NEQ:
return new JsBinaryOperation(JsBinaryOperator.EQ, binary.getArg1(), binary.getArg2());
case REF_EQ:
return inequality(binary.getArg1(), binary.getArg2());
case REF_NEQ:
return equality(binary.getArg1(), binary.getArg2());
case LT:
return greaterThanEq(binary.getArg1(), binary.getArg2());
case LTE:
return greaterThan(binary.getArg1(), binary.getArg2());
case GT:
return lessThanEq(binary.getArg1(), binary.getArg2());
case GTE:
return lessThan(binary.getArg1(), binary.getArg2());
default:
break;
}
}
return negated(expression);
}
@NotNull
public static JsBinaryOperation and(@NotNull JsExpression op1, @NotNull JsExpression op2) {
return new JsBinaryOperation(JsBinaryOperator.AND, op1, op2);
@@ -0,0 +1,12 @@
function box() {
var i = 1;
var sum = 0;
while (i < 10 && sum <= 30) {
sum += i;
i++
}
if (sum != 36) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,18 @@
function box() {
var i = 1;
var sum = 0;
while (true) {
if (i >= 10) {
break;
}
if (sum > 30) {
break;
}
sum += i;
i++
}
if (sum != 36) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,20 @@
var global = "";
function foo(x) {
global += x + ";";
return x;
}
function box() {
var i = 1;
var sum = 0;
do {
sum += i;
i++;
} while (foo(i) < 10 && foo(sum) <= 30);
if (global != "2;1;3;3;4;6;5;10;6;15;7;21;8;28;9;36;") return "fail1: " + global;
if (sum != 36) return "fail2: " + sum;
return "OK"
}
@@ -0,0 +1,26 @@
var global = "";
function foo(x) {
global += x + ";";
return x;
}
function box() {
var i = 1;
var sum = 0;
do {
sum += i;
i++;
if (foo(i) >= 10) {
break;
}
if (foo(sum) > 30) {
break;
}
} while (true);
if (global != "2;1;3;3;4;6;5;10;6;15;7;21;8;28;9;36;") return "fail1: " + global;
if (sum != 36) return "fail2: " + sum;
return "OK"
}
@@ -0,0 +1,24 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
do {
++i;
if (i == 5) {
continue
}
global += ";";
if (foo(i) >= 10) {
break;
}
} while (true);
if (global != ";1;2;3;4;6;7;8;9;10") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,24 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
do {
++i;
if (i == 5) {
continue
}
global += ";";
if (foo(i) >= 10) {
break;
}
} while (true);
if (global != ";1;2;3;4;6;7;8;9;10") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,45 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
var j;
do {
++i;
global += ";";
for (j = 0; j < 2; ++j) {
if (j == 1) {
continue;
}
global += "-";
}
for (k in { a: 2, b: 3 }) {
if (k != "a") {
continue;
}
global += "@";
}
j = 0;
while (j++ < 2) {
if (j == 1) {
continue;
}
global += "$";
}
j = 0;
do {
if (j == 1) {
continue;
}
global += "#";
} while (j++ < 2);
} while (foo(i) < 3);
if (global != ";-@$##1;-@$##2;-@$##3") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,48 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
var j;
do {
++i;
global += ";";
for (j = 0; j < 2; ++j) {
if (j == 1) {
continue;
}
global += "-";
}
for (k in { a: 2, b: 3 }) {
if (k != "a") {
continue;
}
global += "@";
}
j = 0;
while (j++ < 2) {
if (j == 1) {
continue;
}
global += "$";
}
j = 0;
do {
if (j == 1) {
continue;
}
global += "#";
} while (j++ < 2);
if (foo(i) >= 3) {
break;
}
} while (true);
if (global != ";-@$##1;-@$##2;-@$##3") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,15 @@
function box() {
var i = 1;
var sum = 0;
outer: {
while (i < 10) {
sum += i;
i++;
if (sum > 20) break outer;
}
}
if (sum != 21) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,18 @@
function box() {
var i = 1;
var sum = 0;
outer: {
while (true) {
if (i >= 10) {
break;
}
sum += i;
i++;
if (sum > 20) break outer;
}
}
if (sum != 21) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,16 @@
function box() {
var i;
var sum = 0;
var count = 2;
while (count-- > 0) {
i = 1;
while (i < 10) {
sum += i;
i++
}
}
if (sum != 90) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,19 @@
function box() {
var i;
var sum = 0;
var count = 2;
while (count-- > 0) {
i = 1;
while (true) {
if (i >= 10) {
break;
}
sum += i;
i++
}
}
if (sum != 90) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,28 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
var j;
loop: do {
++i;
global += ";";
for (j = 0; j < 2; ++j) {
if (j == 1 && i == 2) {
continue loop;
}
global += "-";
}
if (foo(i) >= 5) {
break;
}
} while (true);
if (global != ";--1;-;--3;--4;--5") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,28 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
var j;
loop: do {
++i;
global += ";";
for (j = 0; j < 2; ++j) {
if (j == 1 && i == 2) {
continue loop;
}
global += "-";
}
if (foo(i) >= 5) {
break;
}
} while (true);
if (global != ";--1;-;--3;--4;--5") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,12 @@
function box() {
var i = 1;
var sum = 0;
while (i < 5 || sum <= 40) {
sum += i;
i++
}
if (sum != 45) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,15 @@
function box() {
var i = 1;
var sum = 0;
while (true) {
if (i >= 5) {
if (sum > 40) break;
}
sum += i;
i++
}
if (sum != 45) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,18 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
do {
++i;
global += ";";
} while (foo(i) < 10);
if (global != ";1;2;3;4;5;6;7;8;9;10") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,21 @@
var global = "";
function foo(x) {
global += x;
return x;
}
function box() {
var i = 0;
do {
++i;
global += ";";
if (foo(i) >= 10) {
break;
}
} while (true);
if (global != ";1;2;3;4;5;6;7;8;9;10") return "fail: " + global;
return "OK"
}
@@ -0,0 +1,12 @@
function box() {
var i = 1;
var sum = 0;
while (i < 10) {
sum += i;
i++
}
if (sum != 45) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,15 @@
function box() {
var i = 1;
var sum = 0;
while (true) {
if (i >= 10) {
break;
}
sum += i;
i++
}
if (sum != 45) return "fail: " + sum;
return "OK"
}
@@ -0,0 +1,20 @@
var global = "";
function foo(x) {
global += x + ";";
return x;
}
function box() {
var i = 1;
var sum = 0;
while (foo(i) < 10 && foo(sum) <= 30) {
sum += i;
i++;
}
if (global != "1;0;2;1;3;3;4;6;5;10;6;15;7;21;8;28;9;36;") return "fail1: " + global;
if (sum != 36) return "fail2: " + sum;
return "OK"
}
@@ -0,0 +1,26 @@
var global = "";
function foo(x) {
global += x + ";";
return x;
}
function box() {
var i = 1;
var sum = 0;
while (true) {
if (foo(i) >= 10) {
break;
}
if (foo(sum) > 30) {
break;
}
sum += i;
i++;
}
if (global != "1;0;2;1;3;3;4;6;5;10;6;15;7;21;8;28;9;36;") return "fail1: " + global;
if (sum != 36) return "fail2: " + sum;
return "OK"
}