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(
|
||||
//{ 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) {
|
||||
|
||||
+3
-1
@@ -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()
|
||||
}
|
||||
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