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:
@@ -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(
|
val optimizations = listOf(
|
||||||
//{ TemporaryAssignmentElimination(root.body).apply() },
|
//{ TemporaryAssignmentElimination(root.body).apply() },
|
||||||
{ RedundantLabelRemoval(root.body).apply() },
|
{ RedundantLabelRemoval(root.body).apply() },
|
||||||
|
{ EmptyStatementElimination(root.body).apply() },
|
||||||
{ WhileConditionFolding(root.body).apply() },
|
{ WhileConditionFolding(root.body).apply() },
|
||||||
{ DoWhileGuardElimination(root.body).apply() },
|
{ DoWhileGuardElimination(root.body).apply() },
|
||||||
{ TemporaryVariableElimination(root).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.*
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
|
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
|
||||||
|
|
||||||
internal class RedundantLabelRemoval(private val root: JsStatement) {
|
internal class RedundantLabelRemoval(private val root: JsStatement) {
|
||||||
private val labelUsages = mutableMapOf<JsName, Int>()
|
private val labelUsages = mutableMapOf<JsName, Int>()
|
||||||
@@ -48,27 +47,10 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
|
|||||||
object : JsVisitorWithContextImpl() {
|
object : JsVisitorWithContextImpl() {
|
||||||
override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) {
|
override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) {
|
||||||
if (x.synthetic) {
|
if (x.synthetic) {
|
||||||
val statementReplacement = perform(x.statement, x.name)
|
x.statement = perform(x.statement, x.name)
|
||||||
if (statementReplacement == null) {
|
if (labelUsages[x.name] ?: 0 == 0) {
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
ctx.removeMe()
|
ctx.replaceMe(x.statement)
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,87 +61,38 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
|
|||||||
}.accept(root)
|
}.accept(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perform(statement: JsStatement, name: JsName): JsStatement? = when (statement) {
|
private fun perform(statement: JsStatement, name: JsName): JsStatement = when (statement) {
|
||||||
is JsBreak ->
|
is JsBreak ->
|
||||||
if (name == statement.label?.name) {
|
if (name == statement.label?.name) {
|
||||||
unuseLabel(name)
|
unuseLabel(name)
|
||||||
null
|
hasChanges = true
|
||||||
|
JsEmpty
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
statement
|
statement
|
||||||
}
|
}
|
||||||
is JsLabel -> {
|
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 -> {
|
is JsIf -> {
|
||||||
val thenRemoved = perform(statement.thenStatement, name) == null
|
statement.thenStatement = perform(statement.thenStatement, name)
|
||||||
val elseStatement = statement.elseStatement
|
statement.elseStatement = statement.elseStatement?.let { perform(it, name) }
|
||||||
val elseRemoved = elseStatement?.let { perform(it, name) == null } ?: false
|
statement
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is JsTry -> {
|
is JsTry -> {
|
||||||
// TODO: optimize finally and catch blocks
|
perform(statement.tryBlock, name)
|
||||||
val finallyBlock = statement.finallyBlock
|
statement
|
||||||
val result = perform(statement.tryBlock, name)
|
|
||||||
if (result != null) {
|
|
||||||
statement
|
|
||||||
}
|
|
||||||
else if (finallyBlock != null && !finallyBlock.isEmpty) {
|
|
||||||
finallyBlock
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> statement
|
else -> statement
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perform(statements: MutableList<JsStatement>, name: JsName): MutableList<JsStatement> {
|
private fun perform(statements: MutableList<JsStatement>, name: JsName) {
|
||||||
val last = statements.lastOrNull()
|
statements.lastOrNull()?.let { statements[statements.lastIndex] = perform(it, name) }
|
||||||
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 useLabel(name: JsName) {
|
private fun useLabel(name: JsName) {
|
||||||
|
|||||||
+3
-1
@@ -18,8 +18,10 @@ package org.jetbrains.kotlin.js.test.optimizer
|
|||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class RedundantLabelRemovalTest : BasicOptimizerTest("redundant-label-removal") {
|
class EmptyStatementEliminationTest : BasicOptimizerTest("empty-statement-elimination") {
|
||||||
@Test fun emptyIfConditionPreserved() = box()
|
@Test fun emptyIfConditionPreserved() = box()
|
||||||
|
|
||||||
@Test fun ifWithEmptyThenAndNoElse() = box()
|
@Test fun ifWithEmptyThenAndNoElse() = box()
|
||||||
|
|
||||||
|
@Test fun emptyBlockEliminated() = box()
|
||||||
}
|
}
|
||||||
js/js.translator/testData/js-optimizer/empty-statement-elimination/emptyBlockEliminated.optimized.js
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
function box() {
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
Vendored
+20
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user