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:
@@ -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() },
|
||||
|
||||
+27
@@ -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()
|
||||
}
|
||||
+22
@@ -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";
|
||||
}
|
||||
+22
@@ -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";
|
||||
}
|
||||
Vendored
+20
@@ -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";
|
||||
}
|
||||
Vendored
+22
@@ -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";
|
||||
}
|
||||
+15
@@ -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";
|
||||
}
|
||||
+17
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user