Implement dead code elimination in JS AST. Repeat optimizations until it's not possible to optimize anything
This commit is contained in:
@@ -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
|
||||
|
||||
+6
-2
@@ -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()
|
||||
}
|
||||
|
||||
+7
-1
@@ -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
|
||||
}
|
||||
|
||||
+10
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user