KT-12275 Add JS optimization that transforms the following code

```
do {
    guard: {
        // do something
        break guard;
        // do something
    }
} while (condition)
```

to

```
do {
    // do something
    continue;
    // do something
} while (condition)
```
This commit is contained in:
Alexey Andreev
2016-05-13 19:06:17 +03:00
parent 1a61115148
commit b3d29adad9
10 changed files with 322 additions and 1 deletions
@@ -130,7 +130,7 @@ internal class ExpressionDecomposer private constructor(
}
else {
// See KT-12275
val guardName = scope.declareFreshName("guard$")
val guardName = scope.declareFreshName("guard\$${(loopLabel?.ident.orEmpty())}")
val label = JsLabel(guardName).apply { synthetic = true }
label.statement = JsBlock(bodyStatements)
addStatements(0, listOf(label))
@@ -0,0 +1,175 @@
/*
* 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.*
/**
* During inlining we can sometimes get the following representation for do..while statement:
*
* do {
* guard: {
* // some logic
* break guard;
* // some logic
* }
* }
* while (condition)
*
* A block labeled `guard` can be eliminated with `break guard` converted to `continue`.
*/
internal class DoWhileGuardElimination(private val root: JsStatement) {
private val guardLabels = mutableSetOf<JsName>()
private var hasChanges = false
private val loopGuardMap = mutableMapOf<JsDoWhile, JsLabel>()
private val guardToLoopLabel = mutableMapOf<JsName, JsName?>()
fun apply(): Boolean {
analyze()
perform()
return hasChanges
}
private fun analyze() {
object : RecursiveJsVisitor() {
override fun visitLabel(x: JsLabel) {
val statement = x.statement
if (statement is JsDoWhile) {
processDoWhile(statement, x.name)
}
else {
super.visitLabel(x)
}
}
override fun visitDoWhile(x: JsDoWhile) = processDoWhile(x, null)
private fun processDoWhile(x: JsDoWhile, label: JsName?) {
val body = x.body
val guard = when (body) {
is JsBlock -> {
val firstStatement = body.statements.first()
if (firstStatement is JsLabel && body.statements.size == 1) {
firstStatement
}
else {
null
}
}
is JsLabel -> body
else -> null
}
if (guard != null) {
// When do..while loop has no label and we encounter break guard from nested loop, we can't
// replace this break with continue. Example:
//
// do {
// guard: {
// for (;;) {
// break guard;
// }
// }
// }
// while (condition)
//
// In this case we get simple `continue` that goes to beginning of `for`, not `do`.
// We can't specify label explicitly, since there's no label on `do`.
// See `js-optimizer/do-while-guard-elimination/innerBreakInLoopWithoutLabel.original.js`
//
// So we don't apply optimization if `do` statement does not have label on it and there's
// a nested loop which has `break guard` statement.
if (label != null || !findBreakInNestedLoop(guard, guard.name)) {
guardLabels += guard.name
loopGuardMap[x] = guard
guardToLoopLabel[guard.name] = label
}
}
body.accept(this)
}
override fun visitFunction(x: JsFunction) { }
}.accept(root)
}
private fun findBreakInNestedLoop(statement: JsStatement, name: JsName): Boolean {
var result = false
statement.accept(object : RecursiveJsVisitor() {
private var loopLevel = 0
override fun visitBreak(x: JsBreak) {
val guardLabel = x.label?.name ?: return
if (guardLabel == name && isInLoop()) {
result = true
}
}
private fun isInLoop() = loopLevel > 0
override fun visitDoWhile(x: JsDoWhile) = enterLoop { super.visitDoWhile(x) }
override fun visitWhile(x: JsWhile) = enterLoop { super.visitWhile(x) }
override fun visitFor(x: JsFor) = enterLoop { super.visitFor(x) }
override fun visitForIn(x: JsForIn) = enterLoop { super.visitForIn(x) }
private inline fun enterLoop(action: () -> Unit) {
loopLevel++
action()
loopLevel--
}
override fun visitFunction(x: JsFunction) { }
override fun visitElement(node: JsNode) {
if (!result) {
super.visitElement(node)
}
}
})
return result
}
private fun perform() {
object : JsVisitorWithContextImpl() {
override fun visit(x: JsDoWhile, ctx: JsContext<JsNode>): Boolean {
loopGuardMap[x]?.let { guard ->
if (guard.name in guardLabels) {
x.body = accept(guard.statement)
hasChanges = true
return false
}
}
return super.visit(x, ctx)
}
override fun visit(x: JsBreak, ctx: JsContext<JsNode>): Boolean {
val name = x.label?.name
if (name in guardLabels) {
val target = guardToLoopLabel[name]
ctx.replaceMe(JsContinue(target?.makeRef()))
hasChanges = true
}
return false
}
}.accept(root)
}
}
@@ -22,6 +22,7 @@ class FunctionPostProcessor(root: JsFunction) {
val optimizations = listOf(
{ TemporaryAssignmentElimination(root.body).apply() },
{ RedundantLabelRemoval(root.body).apply() },
{ DoWhileGuardElimination(root.body).apply() },
{ TemporaryVariableElimination(root).apply() },
{ RedundantBindElimination(root.body).apply() },
{ IfStatementReduction(root.body).apply() },
@@ -0,0 +1,27 @@
/*
* 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 DoWhileGuardEliminationTest : BasicOptimizerTest("do-while-guard-elimination") {
@Test fun simple() = box()
@Test fun innerContinue() = box()
@Test fun innerBreakInLoopWithoutLabel() = box()
}
@@ -0,0 +1,22 @@
function box() {
var i = 0;
var counter = 0;
var sum = 0;
do {
guard$: {
i++;
if (i < 5) {
for (var j = 0; j < 10; ++j) {
counter += j;
if (j == 3 && i == 2) break guard$;
}
}
sum += i;
}
} while (i < 10);
if (sum != 53) return "fail1: " + sum;
if (counter != 141) return "fail2: " + counter;
return "OK";
}
@@ -0,0 +1,22 @@
function box() {
var i = 0;
var counter = 0;
var sum = 0;
do {
guard$: {
i++;
if (i < 5) {
for (var j = 0; j < 10; ++j) {
counter += j;
if (j == 3 && i == 2) break guard$;
}
}
sum += i;
}
} while (i < 10);
if (sum != 53) return "fail1: " + sum;
if (counter != 141) return "fail2: " + counter;
return "OK";
}
@@ -0,0 +1,20 @@
function box() {
var i = 0;
var counter = 0;
var sum = 0;
loop: do {
i++;
if (i < 5) {
for (var j = 0; j < 10; ++j) {
counter += j;
if (j == 3 && i == 2) continue loop;
}
}
sum += i;
} while (i < 10);
if (sum != 53) return "fail1: " + sum;
if (counter != 141) return "fail2: " + counter;
return "OK";
}
@@ -0,0 +1,22 @@
function box() {
var i = 0;
var counter = 0;
var sum = 0;
loop: do {
guard$: {
i++;
if (i < 5) {
for (var j = 0; j < 10; ++j) {
counter += j;
if (j == 3 && i == 2) break guard$;
}
}
sum += i;
}
} while (i < 10);
if (sum != 53) return "fail1: " + sum;
if (counter != 141) return "fail2: " + counter;
return "OK";
}
@@ -0,0 +1,15 @@
function box() {
var i = 0;
var sum = 0;
do {
i++;
if (i == 5) {
continue;
}
sum += i;
} while (i < 10);
if (sum != 50) return "fail: " + sum;
return "OK";
}
@@ -0,0 +1,17 @@
function box() {
var i = 0;
var sum = 0;
do {
guard$: {
i++;
if (i == 5) {
break guard$;
}
sum += i;
}
} while (i < 10);
if (sum != 50) return "fail: " + sum;
return "OK";
}