diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/EmptyStatementElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/EmptyStatementElimination.kt new file mode 100644 index 00000000000..c35f4be646d --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/EmptyStatementElimination.kt @@ -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) { + 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) { + 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) { + val finallyBlock = x.finallyBlock + if (x.tryBlock.isEmpty) { + hasChanges = true + ctx.replaceMe(finallyBlock ?: JsEmpty) + } + } + + override fun endVisit(x: JsSwitch, ctx: JsContext) { + 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) { + 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 + } +} \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt index c8cfdbc2d71..e768286db00 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt @@ -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() }, diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantLabelRemoval.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantLabelRemoval.kt index bed735fcbc6..c5710ebed0e 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantLabelRemoval.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantLabelRemoval.kt @@ -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() @@ -48,27 +47,10 @@ internal class RedundantLabelRemoval(private val root: JsStatement) { object : JsVisitorWithContextImpl() { override fun endVisit(x: JsLabel, ctx: JsContext) { 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, name: JsName): MutableList { - 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, name: JsName) { + statements.lastOrNull()?.let { statements[statements.lastIndex] = perform(it, name) } } private fun useLabel(name: JsName) { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/RedundantLabelRemovalTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/EmptyStatementEliminationTest.kt similarity index 85% rename from js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/RedundantLabelRemovalTest.kt rename to js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/EmptyStatementEliminationTest.kt index 2720593171c..b912e93296d 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/RedundantLabelRemovalTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/EmptyStatementEliminationTest.kt @@ -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() } diff --git a/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyBlockEliminated.optimized.js b/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyBlockEliminated.optimized.js new file mode 100644 index 00000000000..c2965905144 --- /dev/null +++ b/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyBlockEliminated.optimized.js @@ -0,0 +1,3 @@ +function box() { + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyBlockEliminated.original.js b/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyBlockEliminated.original.js new file mode 100644 index 00000000000..d56b234723a --- /dev/null +++ b/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyBlockEliminated.original.js @@ -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; +} \ No newline at end of file diff --git a/js/js.translator/testData/js-optimizer/redundant-label-removal/emptyIfConditionPreserved.optimized.js b/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyIfConditionPreserved.optimized.js similarity index 100% rename from js/js.translator/testData/js-optimizer/redundant-label-removal/emptyIfConditionPreserved.optimized.js rename to js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyIfConditionPreserved.optimized.js diff --git a/js/js.translator/testData/js-optimizer/redundant-label-removal/emptyIfConditionPreserved.original.js b/js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyIfConditionPreserved.original.js similarity index 100% rename from js/js.translator/testData/js-optimizer/redundant-label-removal/emptyIfConditionPreserved.original.js rename to js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyIfConditionPreserved.original.js diff --git a/js/js.translator/testData/js-optimizer/redundant-label-removal/ifWithEmptyThenAndNoElse.optimized.js b/js/js.translator/testData/js-optimizer/empty-statement-elimination/ifWithEmptyThenAndNoElse.optimized.js similarity index 100% rename from js/js.translator/testData/js-optimizer/redundant-label-removal/ifWithEmptyThenAndNoElse.optimized.js rename to js/js.translator/testData/js-optimizer/empty-statement-elimination/ifWithEmptyThenAndNoElse.optimized.js diff --git a/js/js.translator/testData/js-optimizer/redundant-label-removal/ifWithEmptyThenAndNoElse.original.js b/js/js.translator/testData/js-optimizer/empty-statement-elimination/ifWithEmptyThenAndNoElse.original.js similarity index 100% rename from js/js.translator/testData/js-optimizer/redundant-label-removal/ifWithEmptyThenAndNoElse.original.js rename to js/js.translator/testData/js-optimizer/empty-statement-elimination/ifWithEmptyThenAndNoElse.original.js