JS: transfer all empty statement simplifications RedundantLabelRemoval to EmptyStatementElimination, so that empty statements can be eliminated everywhere, not in labeled blocks only.

This commit is contained in:
Alexey Andreev
2017-01-19 18:53:56 +03:00
parent ac0df8eef9
commit c1838d423b
10 changed files with 148 additions and 87 deletions
@@ -0,0 +1,102 @@
/*
* Copyright 2010-2017 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 org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
internal class EmptyStatementElimination(private val root: JsStatement) {
private var hasChanges = false
fun apply(): Boolean {
object : JsVisitorWithContextImpl() {
override fun visit(x: JsFunction, ctx: JsContext<*>) = false
override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) {
if (x.synthetic) {
if (isEmpty(x.statement)) {
ctx.replaceMe(x.statement)
hasChanges = true
}
}
}
override fun endVisit(x: JsBlock, ctx: JsContext<*>) {
processStatements(x.statements)
}
override fun endVisit(x: JsIf, ctx: JsContext<JsNode>) {
val thenEmpty = isEmpty(x.thenStatement)
val elseEmpty = x.elseStatement?.let { isEmpty(it) } ?: true
when {
thenEmpty && elseEmpty -> {
hasChanges = true
ctx.replaceMe(JsAstUtils.asSyntheticStatement(x.ifExpression))
}
elseEmpty -> {
if (x.elseStatement != null) {
hasChanges = true
x.elseStatement = null
}
}
thenEmpty -> {
hasChanges = true
x.thenStatement = x.elseStatement!!
x.elseStatement = null
x.ifExpression = JsAstUtils.notOptimized(x.ifExpression)
}
}
}
override fun endVisit(x: JsTry, ctx: JsContext<JsNode>) {
val finallyBlock = x.finallyBlock
if (x.tryBlock.isEmpty) {
hasChanges = true
ctx.replaceMe(finallyBlock ?: JsEmpty)
}
}
override fun endVisit(x: JsSwitch, ctx: JsContext<JsNode>) {
for (case in x.cases) {
processStatements(case.statements)
}
if (x.cases.dropLast(1).none { it is JsDefault } && x.cases.dropLast(1).all { it.statements.isEmpty() }) {
hasChanges = true
val conditionStatement = JsAstUtils.asSyntheticStatement(x.expression)
ctx.replaceMe(JsBlock(listOf(conditionStatement) + x.cases.last().statements))
}
}
private fun processStatements(statements: MutableList<JsStatement>) {
for ((index, statement) in statements.withIndex().reversed()) {
if (statement is JsEmpty) {
statements.removeAt(index)
hasChanges = true
}
else if (statement is JsBlock) {
statements.removeAt(index)
statements.addAll(index, statement.statements)
}
}
}
private fun isEmpty(statement: JsStatement) = statement is JsBlock && statement.isEmpty || statement is JsEmpty
}.accept(root)
return hasChanges
}
}
@@ -22,6 +22,7 @@ class FunctionPostProcessor(val root: JsFunction) {
val optimizations = listOf(
//{ TemporaryAssignmentElimination(root.body).apply() },
{ RedundantLabelRemoval(root.body).apply() },
{ EmptyStatementElimination(root.body).apply() },
{ WhileConditionFolding(root.body).apply() },
{ DoWhileGuardElimination(root.body).apply() },
{ TemporaryVariableElimination(root).apply() },
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.js.inline.clean
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
internal class RedundantLabelRemoval(private val root: JsStatement) {
private val labelUsages = mutableMapOf<JsName, Int>()
@@ -48,27 +47,10 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
object : JsVisitorWithContextImpl() {
override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) {
if (x.synthetic) {
val statementReplacement = perform(x.statement, x.name)
if (statementReplacement == null) {
x.statement = perform(x.statement, x.name)
if (labelUsages[x.name] ?: 0 == 0) {
hasChanges = true
ctx.removeMe()
}
else if (labelUsages[x.name] ?: 0 == 0) {
val replacement = statementReplacement
if (replacement is JsBlock) {
hasChanges = true
ctx.addPrevious(replacement.statements)
ctx.removeMe()
}
else {
if (replacement != ctx.currentNode) {
hasChanges = true
ctx.replaceMe(replacement)
}
}
}
else {
x.statement = statementReplacement
ctx.replaceMe(x.statement)
}
}
@@ -79,87 +61,38 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
}.accept(root)
}
private fun perform(statement: JsStatement, name: JsName): JsStatement? = when (statement) {
private fun perform(statement: JsStatement, name: JsName): JsStatement = when (statement) {
is JsBreak ->
if (name == statement.label?.name) {
unuseLabel(name)
null
hasChanges = true
JsEmpty
}
else {
statement
}
is JsLabel -> {
perform(statement.statement, name)?.let { statement }
perform(statement.statement, name)
statement
}
is JsBlock -> {
perform(statement.statements, name)
statement
}
is JsBlock ->
if (perform(statement.statements, name).isEmpty()) {
null
}
else {
statement
}
is JsIf -> {
val thenRemoved = perform(statement.thenStatement, name) == null
val elseStatement = statement.elseStatement
val elseRemoved = elseStatement?.let { perform(it, name) == null } ?: false
when {
thenRemoved && (elseRemoved || elseStatement == null) -> {
hasChanges = true
JsAstUtils.asSyntheticStatement(statement.ifExpression)
}
elseRemoved -> {
hasChanges = true
statement.elseStatement = null
statement
}
thenRemoved -> {
hasChanges = true
statement.thenStatement = elseStatement ?: JsEmpty
statement.elseStatement = null
statement.ifExpression = JsAstUtils.not(statement.ifExpression)
statement
}
else -> statement
}
statement.thenStatement = perform(statement.thenStatement, name)
statement.elseStatement = statement.elseStatement?.let { perform(it, name) }
statement
}
is JsTry -> {
// TODO: optimize finally and catch blocks
val finallyBlock = statement.finallyBlock
val result = perform(statement.tryBlock, name)
if (result != null) {
statement
}
else if (finallyBlock != null && !finallyBlock.isEmpty) {
finallyBlock
}
else {
null
}
perform(statement.tryBlock, name)
statement
}
else -> statement
}
private fun perform(statements: MutableList<JsStatement>, name: JsName): MutableList<JsStatement> {
val last = statements.lastOrNull()
val lastOptimized = last?.let { perform(it, name) }
if (lastOptimized != last) {
if (lastOptimized == null) {
hasChanges = true
statements.removeAt(statements.lastIndex)
}
else if (lastOptimized is JsBlock) {
hasChanges = true
statements.removeAt(statements.lastIndex)
statements.addAll(lastOptimized.statements)
}
else {
if (statements[statements.lastIndex] != lastOptimized) {
hasChanges = true
statements[statements.lastIndex] = lastOptimized
}
}
}
return statements
private fun perform(statements: MutableList<JsStatement>, name: JsName) {
statements.lastOrNull()?.let { statements[statements.lastIndex] = perform(it, name) }
}
private fun useLabel(name: JsName) {
@@ -18,8 +18,10 @@ package org.jetbrains.kotlin.js.test.optimizer
import org.junit.Test
class RedundantLabelRemovalTest : BasicOptimizerTest("redundant-label-removal") {
class EmptyStatementEliminationTest : BasicOptimizerTest("empty-statement-elimination") {
@Test fun emptyIfConditionPreserved() = box()
@Test fun ifWithEmptyThenAndNoElse() = box()
@Test fun emptyBlockEliminated() = box()
}
@@ -0,0 +1,3 @@
function box() {
return "OK";
}
@@ -0,0 +1,20 @@
function box() {
{}
{
{}
{}
}
var $x = "OK";
if ($x == "123") {}
else if ($x == "234") {} else {}
if ($x == "qwe") {}
switch ($x) {
case 1:
case 2:
default:
}
return $x;
}