diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/DeadCodeElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/DeadCodeElimination.kt new file mode 100644 index 00000000000..1d917491982 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/DeadCodeElimination.kt @@ -0,0 +1,206 @@ +/* + * 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.* + +internal class DeadCodeElimination(private val root: JsStatement) { + var hasChanges = false + + fun apply(): Boolean { + EliminationVisitor().accept(root) + return hasChanges + } + + inner class EliminationVisitor : RecursiveJsVisitor() { + var breakLabels = mutableSetOf() + var localBreakExists = false + var canContinue = false + + override fun visitBreak(x: JsBreak) { + val name = x.label?.name + if (name != null) { + breakLabels.add(name) + } + else { + localBreakExists = true + } + canContinue = false + } + + override fun visitContinue(x: JsContinue) { + canContinue = false + } + + override fun visitLabel(x: JsLabel) { + accept(x.statement) + if (!canContinue && x.name in breakLabels) { + canContinue = true + } + breakLabels.remove(x.name) + } + + override fun visitBlock(x: JsBlock) { + canContinue = true + for ((index, statement) in x.statements.withIndex()) { + accept(statement) + if (!canContinue) { + val removedStatements = x.statements.subList(index + 1, x.statements.size) + if (removedStatements.isNotEmpty()) { + hasChanges = true + removedStatements.clear() + } + break + } + } + } + + override fun visitWhile(x: JsWhile) { + EliminationVisitor().accept(x.condition) + + visitLoop(x.body) { + val condition = x.condition + condition !is JsLiteral.JsBooleanLiteral || !condition.value + } + } + + override fun visitDoWhile(x: JsDoWhile) = visitWhile(x) + + override fun visitFor(x: JsFor) { + EliminationVisitor().accept(x.condition) + EliminationVisitor().accept(x.initExpression) + EliminationVisitor().accept(x.initVars) + EliminationVisitor().accept(x.incrementExpression) + + visitLoop(x.body) { true } + } + + override fun visitForIn(x: JsForIn) { + EliminationVisitor().accept(x.iterExpression) + visitLoop(x.body) { true } + } + + private fun visitLoop(body: JsStatement?, additionalExitCondition: () -> Boolean) { + val localBreakExistsBackup = localBreakExists + + localBreakExists = false + if (body != null) { + accept(body) + } + if (!canContinue) { + canContinue = additionalExitCondition() || localBreakExists + } + + localBreakExists = localBreakExistsBackup + } + + override fun visitIf(x: JsIf) { + EliminationVisitor().accept(x.ifExpression) + + var result = false + + accept(x.thenStatement) + if (canContinue) { + result = true + } + + val elseStatement = x.elseStatement + if (elseStatement != null) { + accept(x.elseStatement) + if (canContinue) { + result = true + } + } + else { + result = true + } + + canContinue = result + } + + override fun visitTry(x: JsTry) { + var result = false + + accept(x.tryBlock) + if (canContinue) { + result = true + } + + for (catchBlock in x.catches) { + accept(catchBlock.body) + if (canContinue) { + result = true + } + } + + val finallyBlock = x.finallyBlock + if (finallyBlock != null) { + accept(finallyBlock) + if (!canContinue) { + result = false + } + } + + canContinue = result + } + + override fun visitExpressionStatement(x: JsExpressionStatement) { + EliminationVisitor().accept(x.expression) + canContinue = true + } + + override fun visit(x: JsSwitch) { + EliminationVisitor().accept(x.expression) + val localBreakExistsBackup = localBreakExists + + var defaultCanContinue = true + var allCasesCantContinue = true + + for (caseBlock in x.cases) { + canContinue = true + caseBlock.statements.forEach { accept(it) } + + if (!canContinue && localBreakExists) { + canContinue = true + } + + if (caseBlock is JsDefault) { + defaultCanContinue = canContinue + } + else if (allCasesCantContinue && canContinue) { + allCasesCantContinue = false + } + } + + canContinue = !allCasesCantContinue || defaultCanContinue + localBreakExists = localBreakExistsBackup + } + + override fun visitThrow(x: JsThrow) { + canContinue = false + } + + override fun visitReturn(x: JsReturn) { + canContinue = false + } + + override fun visitVars(x: JsVars) { + x.vars.forEach { EliminationVisitor().accept(it) } + canContinue = true + } + } +} 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 46ac9ab8674..3682191eeca 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 @@ -20,9 +20,13 @@ import com.google.dart.compiler.backend.js.ast.JsBlock class FunctionPostProcessor(private val root: JsBlock) { fun apply() { - TemporaryAssignmentElimination(root).apply() - RedundantLabelRemoval(root).apply() - TemporaryVariableElimination(root).apply() - RedundantVariableDeclarationElimination(root).apply() + do { + var hasChanges = false + hasChanges = hasChanges or TemporaryAssignmentElimination(root).apply() + hasChanges = hasChanges or RedundantLabelRemoval(root).apply() + hasChanges = hasChanges or TemporaryVariableElimination(root).apply() + hasChanges = hasChanges or DeadCodeElimination(root).apply() + hasChanges = hasChanges or RedundantVariableDeclarationElimination(root).apply() + } while (hasChanges) } } \ No newline at end of file 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 d7a4161729c..603da1f3579 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 @@ -22,10 +22,12 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils internal class RedundantLabelRemoval(private val root: JsStatement) { private val labelUsages = mutableMapOf() + private var hasChanges = false - fun apply() { + fun apply(): Boolean { analyze() perform() + return hasChanges } private fun analyze() { @@ -48,16 +50,21 @@ internal class RedundantLabelRemoval(private val root: JsStatement) { if (x.synthetic) { val statementReplacement = perform(x.statement, x.name) if (statementReplacement == null) { + 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 { - ctx.replaceMe(replacement) + if (replacement != ctx.currentNode) { + hasChanges = true + ctx.replaceMe(replacement) + } } } else { @@ -93,14 +100,16 @@ internal class RedundantLabelRemoval(private val root: JsStatement) { } is JsIf -> { val thenRemoved = perform(statement.thenStatement, name) == null - val elseRemoved = statement.elseStatement?.let { perform(it, name) } == null + val elseRemoved = statement.elseStatement?.let { perform(it, name) } == null && statement.elseStatement != null when { thenRemoved && elseRemoved -> null elseRemoved -> { + hasChanges = true statement.elseStatement = null statement } thenRemoved -> { + hasChanges = true statement.thenStatement = statement.elseStatement statement.elseStatement = null statement.ifExpression = JsAstUtils.negated(statement.ifExpression) @@ -130,14 +139,19 @@ internal class RedundantLabelRemoval(private val root: JsStatement) { 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 { - statements[statements.lastIndex] = lastOptimized + if (statements[statements.lastIndex] != lastOptimized) { + hasChanges = true + statements[statements.lastIndex] = lastOptimized + } } } return statements diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt index 999f3b2b97c..bd83ec75e04 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/RedundantVariableDeclarationElimination.kt @@ -22,10 +22,12 @@ import org.jetbrains.kotlin.js.inline.util.collectFreeVariables internal class RedundantVariableDeclarationElimination(private val root: JsStatement) { private val usages = mutableSetOf() + private var hasChanges = false - fun apply() { + fun apply(): Boolean { analyze() perform() + return hasChanges } private fun analyze() { @@ -53,7 +55,9 @@ internal class RedundantVariableDeclarationElimination(private val root: JsState object : JsVisitorWithContextImpl() { override fun endVisit(x: JsVars, ctx: JsContext<*>) { if (x.synthetic) { - x.vars.removeAll { it.initExpression == null && it.name !in usages } + if (x.vars.removeAll { it.initExpression == null && it.name !in usages }) { + hasChanges = true + } if (x.vars.isEmpty()) { ctx.removeMe() } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt index d67ac7b471a..0ae44945771 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt @@ -29,11 +29,13 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) { private val statementsToRemove = mutableSetOf() private val mappedUsages = mutableMapOf() private val syntheticNames = mutableSetOf() + private var hasChanges = false - fun apply() { + fun apply(): Boolean { analyze() process() generateDeclarations() + return hasChanges } private fun analyze() { @@ -149,6 +151,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) { object : JsVisitorWithContextImpl() { override fun visit(x: JsExpressionStatement, ctx: JsContext): Boolean { if (x in statementsToRemove) { + hasChanges = true ctx.removeMe() return false } @@ -185,6 +188,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) { JsExpressionStatement(JsAstUtils.assignment(usage.target, value).source(x.expression.source)) } } + hasChanges = true ctx.replaceMe(replacement) statementsToRemove += usage.statements return false; @@ -195,6 +199,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) { override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean { if (x in statementsToRemove) { + hasChanges = true ctx.removeMe() return false } @@ -203,6 +208,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) { override fun visit(x: JsVars, ctx: JsContext<*>): Boolean { if (x in statementsToRemove) { + hasChanges = true ctx.removeMe() return false } diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt index fcd995eb41f..cedb951a5eb 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt @@ -27,10 +27,12 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { private val usages = mutableMapOf() private val definedValues = mutableMapOf() private val temporary = mutableSetOf() + private var hasChanges = false - fun apply() { + fun apply(): Boolean { analyze() perform() + return hasChanges } private fun analyze() { @@ -136,6 +138,7 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression) if (assignment != null) { if (shouldConsiderTemporary(assignment.first)) { + hasChanges = true ctx.removeMe() return false } @@ -146,8 +149,11 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { override fun visit(x: JsVars, ctx: JsContext<*>): Boolean { val filteredVars = x.vars.filter { it.initExpression == null || !shouldConsiderTemporary(it.name) } - x.vars.clear() - x.vars.addAll(filteredVars) + if (x.vars.size != filteredVars.size) { + hasChanges = true + x.vars.clear() + x.vars.addAll(filteredVars) + } if (x.vars.isEmpty()) { ctx.removeMe() } @@ -158,6 +164,7 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { override fun visit(x: JsNameRef, ctx: JsContext): Boolean { val name = x.name if (x.qualifier == null && name != null && shouldConsiderTemporary(name)) { + hasChanges = true val newExpr = definedValues[name]!! ctx.replaceMe(accept(newExpr)) usages[name] = usages[name]!! - 1