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) {
|
class FunctionPostProcessor(private val root: JsBlock) {
|
||||||
fun apply() {
|
fun apply() {
|
||||||
TemporaryAssignmentElimination(root).apply()
|
do {
|
||||||
RedundantLabelRemoval(root).apply()
|
var hasChanges = false
|
||||||
TemporaryVariableElimination(root).apply()
|
hasChanges = hasChanges or TemporaryAssignmentElimination(root).apply()
|
||||||
RedundantVariableDeclarationElimination(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) {
|
internal class RedundantLabelRemoval(private val root: JsStatement) {
|
||||||
private val labelUsages = mutableMapOf<JsName, Int>()
|
private val labelUsages = mutableMapOf<JsName, Int>()
|
||||||
|
private var hasChanges = false
|
||||||
|
|
||||||
fun apply() {
|
fun apply(): Boolean {
|
||||||
analyze()
|
analyze()
|
||||||
perform()
|
perform()
|
||||||
|
return hasChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun analyze() {
|
private fun analyze() {
|
||||||
@@ -48,16 +50,21 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
|
|||||||
if (x.synthetic) {
|
if (x.synthetic) {
|
||||||
val statementReplacement = perform(x.statement, x.name)
|
val statementReplacement = perform(x.statement, x.name)
|
||||||
if (statementReplacement == null) {
|
if (statementReplacement == null) {
|
||||||
|
hasChanges = true
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
}
|
}
|
||||||
else if (labelUsages[x.name] ?: 0 == 0) {
|
else if (labelUsages[x.name] ?: 0 == 0) {
|
||||||
val replacement = statementReplacement
|
val replacement = statementReplacement
|
||||||
if (replacement is JsBlock) {
|
if (replacement is JsBlock) {
|
||||||
|
hasChanges = true
|
||||||
ctx.addPrevious(replacement.statements)
|
ctx.addPrevious(replacement.statements)
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ctx.replaceMe(replacement)
|
if (replacement != ctx.currentNode) {
|
||||||
|
hasChanges = true
|
||||||
|
ctx.replaceMe(replacement)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -93,14 +100,16 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
|
|||||||
}
|
}
|
||||||
is JsIf -> {
|
is JsIf -> {
|
||||||
val thenRemoved = perform(statement.thenStatement, name) == null
|
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 {
|
when {
|
||||||
thenRemoved && elseRemoved -> null
|
thenRemoved && elseRemoved -> null
|
||||||
elseRemoved -> {
|
elseRemoved -> {
|
||||||
|
hasChanges = true
|
||||||
statement.elseStatement = null
|
statement.elseStatement = null
|
||||||
statement
|
statement
|
||||||
}
|
}
|
||||||
thenRemoved -> {
|
thenRemoved -> {
|
||||||
|
hasChanges = true
|
||||||
statement.thenStatement = statement.elseStatement
|
statement.thenStatement = statement.elseStatement
|
||||||
statement.elseStatement = null
|
statement.elseStatement = null
|
||||||
statement.ifExpression = JsAstUtils.negated(statement.ifExpression)
|
statement.ifExpression = JsAstUtils.negated(statement.ifExpression)
|
||||||
@@ -130,14 +139,19 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
|
|||||||
val lastOptimized = last?.let { perform(it, name) }
|
val lastOptimized = last?.let { perform(it, name) }
|
||||||
if (lastOptimized != last) {
|
if (lastOptimized != last) {
|
||||||
if (lastOptimized == null) {
|
if (lastOptimized == null) {
|
||||||
|
hasChanges = true
|
||||||
statements.removeAt(statements.lastIndex)
|
statements.removeAt(statements.lastIndex)
|
||||||
}
|
}
|
||||||
else if (lastOptimized is JsBlock) {
|
else if (lastOptimized is JsBlock) {
|
||||||
|
hasChanges = true
|
||||||
statements.removeAt(statements.lastIndex)
|
statements.removeAt(statements.lastIndex)
|
||||||
statements.addAll(lastOptimized.statements)
|
statements.addAll(lastOptimized.statements)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
statements[statements.lastIndex] = lastOptimized
|
if (statements[statements.lastIndex] != lastOptimized) {
|
||||||
|
hasChanges = true
|
||||||
|
statements[statements.lastIndex] = lastOptimized
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return statements
|
return statements
|
||||||
|
|||||||
+6
-2
@@ -22,10 +22,12 @@ import org.jetbrains.kotlin.js.inline.util.collectFreeVariables
|
|||||||
|
|
||||||
internal class RedundantVariableDeclarationElimination(private val root: JsStatement) {
|
internal class RedundantVariableDeclarationElimination(private val root: JsStatement) {
|
||||||
private val usages = mutableSetOf<JsName>()
|
private val usages = mutableSetOf<JsName>()
|
||||||
|
private var hasChanges = false
|
||||||
|
|
||||||
fun apply() {
|
fun apply(): Boolean {
|
||||||
analyze()
|
analyze()
|
||||||
perform()
|
perform()
|
||||||
|
return hasChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun analyze() {
|
private fun analyze() {
|
||||||
@@ -53,7 +55,9 @@ internal class RedundantVariableDeclarationElimination(private val root: JsState
|
|||||||
object : JsVisitorWithContextImpl() {
|
object : JsVisitorWithContextImpl() {
|
||||||
override fun endVisit(x: JsVars, ctx: JsContext<*>) {
|
override fun endVisit(x: JsVars, ctx: JsContext<*>) {
|
||||||
if (x.synthetic) {
|
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()) {
|
if (x.vars.isEmpty()) {
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -29,11 +29,13 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
|
|||||||
private val statementsToRemove = mutableSetOf<JsStatement>()
|
private val statementsToRemove = mutableSetOf<JsStatement>()
|
||||||
private val mappedUsages = mutableMapOf<JsName, Usage>()
|
private val mappedUsages = mutableMapOf<JsName, Usage>()
|
||||||
private val syntheticNames = mutableSetOf<JsName>()
|
private val syntheticNames = mutableSetOf<JsName>()
|
||||||
|
private var hasChanges = false
|
||||||
|
|
||||||
fun apply() {
|
fun apply(): Boolean {
|
||||||
analyze()
|
analyze()
|
||||||
process()
|
process()
|
||||||
generateDeclarations()
|
generateDeclarations()
|
||||||
|
return hasChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun analyze() {
|
private fun analyze() {
|
||||||
@@ -149,6 +151,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
|
|||||||
object : JsVisitorWithContextImpl() {
|
object : JsVisitorWithContextImpl() {
|
||||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
|
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
|
||||||
if (x in statementsToRemove) {
|
if (x in statementsToRemove) {
|
||||||
|
hasChanges = true
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -185,6 +188,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
|
|||||||
JsExpressionStatement(JsAstUtils.assignment(usage.target, value).source(x.expression.source))
|
JsExpressionStatement(JsAstUtils.assignment(usage.target, value).source(x.expression.source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hasChanges = true
|
||||||
ctx.replaceMe(replacement)
|
ctx.replaceMe(replacement)
|
||||||
statementsToRemove += usage.statements
|
statementsToRemove += usage.statements
|
||||||
return false;
|
return false;
|
||||||
@@ -195,6 +199,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
|
|||||||
|
|
||||||
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
|
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
|
||||||
if (x in statementsToRemove) {
|
if (x in statementsToRemove) {
|
||||||
|
hasChanges = true
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -203,6 +208,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
|
|||||||
|
|
||||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||||
if (x in statementsToRemove) {
|
if (x in statementsToRemove) {
|
||||||
|
hasChanges = true
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -27,10 +27,12 @@ internal class TemporaryVariableElimination(private val root: JsStatement) {
|
|||||||
private val usages = mutableMapOf<JsName, Int>()
|
private val usages = mutableMapOf<JsName, Int>()
|
||||||
private val definedValues = mutableMapOf<JsName, JsExpression>()
|
private val definedValues = mutableMapOf<JsName, JsExpression>()
|
||||||
private val temporary = mutableSetOf<JsName>()
|
private val temporary = mutableSetOf<JsName>()
|
||||||
|
private var hasChanges = false
|
||||||
|
|
||||||
fun apply() {
|
fun apply(): Boolean {
|
||||||
analyze()
|
analyze()
|
||||||
perform()
|
perform()
|
||||||
|
return hasChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun analyze() {
|
private fun analyze() {
|
||||||
@@ -136,6 +138,7 @@ internal class TemporaryVariableElimination(private val root: JsStatement) {
|
|||||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||||
if (assignment != null) {
|
if (assignment != null) {
|
||||||
if (shouldConsiderTemporary(assignment.first)) {
|
if (shouldConsiderTemporary(assignment.first)) {
|
||||||
|
hasChanges = true
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -146,8 +149,11 @@ internal class TemporaryVariableElimination(private val root: JsStatement) {
|
|||||||
|
|
||||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||||
val filteredVars = x.vars.filter { it.initExpression == null || !shouldConsiderTemporary(it.name) }
|
val filteredVars = x.vars.filter { it.initExpression == null || !shouldConsiderTemporary(it.name) }
|
||||||
x.vars.clear()
|
if (x.vars.size != filteredVars.size) {
|
||||||
x.vars.addAll(filteredVars)
|
hasChanges = true
|
||||||
|
x.vars.clear()
|
||||||
|
x.vars.addAll(filteredVars)
|
||||||
|
}
|
||||||
if (x.vars.isEmpty()) {
|
if (x.vars.isEmpty()) {
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
}
|
}
|
||||||
@@ -158,6 +164,7 @@ internal class TemporaryVariableElimination(private val root: JsStatement) {
|
|||||||
override fun visit(x: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
override fun visit(x: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
||||||
val name = x.name
|
val name = x.name
|
||||||
if (x.qualifier == null && name != null && shouldConsiderTemporary(name)) {
|
if (x.qualifier == null && name != null && shouldConsiderTemporary(name)) {
|
||||||
|
hasChanges = true
|
||||||
val newExpr = definedValues[name]!!
|
val newExpr = definedValues[name]!!
|
||||||
ctx.replaceMe(accept(newExpr))
|
ctx.replaceMe(accept(newExpr))
|
||||||
usages[name] = usages[name]!! - 1
|
usages[name] = usages[name]!! - 1
|
||||||
|
|||||||
Reference in New Issue
Block a user