Implement dead code elimination in JS AST. Repeat optimizations until it's not possible to optimize anything

This commit is contained in:
Alexey Andreev
2016-03-17 16:37:47 +03:00
parent b5ab8f8488
commit 33baa1d774
6 changed files with 255 additions and 14 deletions
@@ -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<JsName>()
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
}
}
}
@@ -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)
}
}
@@ -22,10 +22,12 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
internal class RedundantLabelRemoval(private val root: JsStatement) {
private val labelUsages = mutableMapOf<JsName, Int>()
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
@@ -22,10 +22,12 @@ import org.jetbrains.kotlin.js.inline.util.collectFreeVariables
internal class RedundantVariableDeclarationElimination(private val root: JsStatement) {
private val usages = mutableSetOf<JsName>()
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()
}
@@ -29,11 +29,13 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
private val statementsToRemove = mutableSetOf<JsStatement>()
private val mappedUsages = mutableMapOf<JsName, Usage>()
private val syntheticNames = mutableSetOf<JsName>()
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<JsNode>): 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
}
@@ -27,10 +27,12 @@ internal class TemporaryVariableElimination(private val root: JsStatement) {
private val usages = mutableMapOf<JsName, Int>()
private val definedValues = mutableMapOf<JsName, JsExpression>()
private val temporary = mutableSetOf<JsName>()
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<JsNode>): 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