Refactor JS optimizer to use RecursiveJsVisitor where possible and introduce more self-descriptive names in TemporaryAssignmentElimination. Add TODOs and comments for code pieces with non-obvious meaning.

This commit is contained in:
Alexey Andreev
2016-04-01 11:43:45 +03:00
parent 4d126096a1
commit 0caff1e504
5 changed files with 77 additions and 75 deletions
@@ -86,6 +86,8 @@ internal class DeadCodeElimination(private val root: JsStatement) {
EliminationVisitor().accept(x.initVars) EliminationVisitor().accept(x.initVars)
EliminationVisitor().accept(x.incrementExpression) EliminationVisitor().accept(x.incrementExpression)
// TODO: We may also check if condition is `true` constant or missing, which means this loop is infinite, i.e.
// code after this loop can be safely deleted when loop does not contain break statements.
visitLoop(x.body) { true } visitLoop(x.body) { true }
} }
@@ -19,16 +19,19 @@ package org.jetbrains.kotlin.js.inline.clean
import com.google.dart.compiler.backend.js.ast.JsBlock import com.google.dart.compiler.backend.js.ast.JsBlock
class FunctionPostProcessor(private val root: JsBlock) { class FunctionPostProcessor(private val root: JsBlock) {
val optimizations = listOf(
{ TemporaryAssignmentElimination(root).apply() },
{ RedundantLabelRemoval(root).apply() },
{ TemporaryVariableElimination(root).apply() },
{ IfStatementReduction(root).apply() },
{ DeadCodeElimination(root).apply() },
{ RedundantVariableDeclarationElimination(root).apply() }
)
// TODO: reduce to A || B, A && B if possible
fun apply() { fun apply() {
do { do {
var hasChanges = false val hasChanges = optimizations.fold(false) { existing, f -> existing or f() }
hasChanges = hasChanges or TemporaryAssignmentElimination(root).apply()
hasChanges = hasChanges or RedundantLabelRemoval(root).apply()
hasChanges = hasChanges or TemporaryVariableElimination(root).apply()
hasChanges = hasChanges or IfStatementReduction(root).apply()
// TODO: reduce to A || B, A && B if possible
hasChanges = hasChanges or DeadCodeElimination(root).apply()
hasChanges = hasChanges or RedundantVariableDeclarationElimination(root).apply()
} while (hasChanges) } while (hasChanges)
} }
} }
@@ -100,7 +100,7 @@ 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 && statement.elseStatement != null val elseRemoved = statement.elseStatement?.let { perform(it, name) == null } ?: false
when { when {
thenRemoved && elseRemoved -> null thenRemoved && elseRemoved -> null
elseRemoved -> { elseRemoved -> {
@@ -119,6 +119,7 @@ internal class RedundantLabelRemoval(private val root: JsStatement) {
} }
} }
is JsTry -> { is JsTry -> {
// TODO: optimize finally and catch blocks
val finallyBlock = statement.finallyBlock val finallyBlock = statement.finallyBlock
val result = perform(statement.tryBlock, name) val result = perform(statement.tryBlock, name)
if (result != null) { if (result != null) {
@@ -43,75 +43,78 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
private fun analyze() { private fun analyze() {
namesToProcess.addAll(collectDefinedNames(root)) namesToProcess.addAll(collectDefinedNames(root))
object : JsVisitorWithContextImpl() { object : RecursiveJsVisitor() {
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean { override fun visitReturn(x: JsReturn) {
val returnExpr = x.expression val returnExpr = x.expression
if (returnExpr != null) { if (returnExpr != null) {
tryRecord(returnExpr, Usage.Return(x)) tryRecord(returnExpr, Usage.Return(x))
} }
return super.visit(x, ctx) super.visitReturn(x)
} }
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean { override fun visitExpressionStatement(x: JsExpressionStatement) {
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression) val variableAssignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
if (assignment != null) { if (variableAssignment != null) {
val (name, value) = assignment val (name, value) = variableAssignment
val usage = Usage.Assignment(x, name) val usage = Usage.VariableAssignment(x, name)
if (x.synthetic) { if (x.synthetic) {
syntheticNames += name syntheticNames += name
} }
tryRecord(value, usage) tryRecord(value, usage)
accept(value) accept(value)
return false // Don't visit LHS, since it's already treated as a temporary variable
return
} }
val mutation = JsAstUtils.decomposeAssignment(x.expression) val propertyMutation = JsAstUtils.decomposeAssignment(x.expression)
if (mutation != null) { if (propertyMutation != null) {
val (target, value) = mutation val (target, value) = propertyMutation
if (!target.canHaveSideEffect()) { if (!target.canHaveSideEffect()) {
val usage = Usage.Mutation(x, target) val usage = Usage.PropertyMutation(x, target)
tryRecord(value, usage) tryRecord(value, usage)
accept(value) accept(value)
return false return
} }
} }
return super.visit(x, ctx) return super.visitExpressionStatement(x)
} }
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean { override fun visitVars(x: JsVars) {
// TODO: generalize for multliple declarations per one statement
if (x.vars.size == 1) { if (x.vars.size == 1) {
val declaration = x.vars[0] val declaration = x.vars[0]
val initExpression = declaration.initExpression val initExpression = declaration.initExpression
if (initExpression != null) { if (initExpression != null) {
tryRecord(initExpression, Usage.Declaration(x, declaration.name)) tryRecord(initExpression, Usage.VariableDeclaration(x, declaration.name))
} }
// TODO: generalize for case when single JsVar is synthetic
if (x.synthetic) { if (x.synthetic) {
syntheticNames += declaration.name syntheticNames += declaration.name
} }
} }
x.vars.forEach { v -> v?.initExpression?.let { accept(it) } } x.vars.forEach { v -> v?.initExpression?.let { accept(it) } }
return false
} }
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean { override fun visitNameRef(nameRef: JsNameRef) {
val name = x.name val name = nameRef.name
if (name != null && x.qualifier == null) { if (name != null && nameRef.qualifier == null) {
use(name) use(name)
return false return
} }
return super.visit(x, ctx) return super.visitNameRef(nameRef)
} }
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean { override fun visitFunction(x: JsFunction) {
// Don't visit function body, but mark its free variables as used two times, so that further stages won't treat
// these variables as temporary.
x.collectFreeVariables().forEach { use(it); use(it) } x.collectFreeVariables().forEach { use(it); use(it) }
return false
} }
override fun visit(x: JsBreak, ctx: JsContext<*>) = false override fun visitBreak(x: JsBreak) { }
override fun visit(x: JsContinue, ctx: JsContext<*>) = false override fun visitContinue(x: JsContinue) { }
}.accept(root) }.accept(root)
usages.keys.retainAll(syntheticNames) usages.keys.retainAll(syntheticNames)
@@ -119,11 +122,11 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
private fun getUsage(name: JsName): Usage? { private fun getUsage(name: JsName): Usage? {
return mappedUsages.getOrPut(name) { return mappedUsages.getOrPut(name) {
if (usageCount[name] ?: 0 != 1) return null if (usageCount[name] != 1) return null
val usage = usages[name] val usage = usages[name]
return when (usage) { return when (usage) {
is Usage.Assignment -> { is Usage.VariableAssignment -> {
val result = getUsage(usage.target) val result = getUsage(usage.target)
if (result != null) { if (result != null) {
result.statements.addAll(usage.statements) result.statements.addAll(usage.statements)
@@ -133,7 +136,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
usage usage
} }
} }
is Usage.Declaration -> { is Usage.VariableDeclaration -> {
val result = getUsage(usage.target) val result = getUsage(usage.target)
if (result != null) { if (result != null) {
result.statements.addAll(usage.statements) result.statements.addAll(usage.statements)
@@ -151,22 +154,16 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
private fun calculateDeclarations() { private fun calculateDeclarations() {
usages.keys.forEach { getUsage(it) } usages.keys.forEach { getUsage(it) }
object : JsVisitorWithContextImpl() { object : RecursiveJsVisitor() {
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean { override fun visitExpressionStatement(x: JsExpressionStatement) {
if (x in statementsToRemove) {
hasChanges = true
ctx.removeMe()
return false
}
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression) val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
if (assignment != null) { if (assignment != null) {
val usage = getUsage(assignment.first) val usage = getUsage(assignment.first)
if (usage is Usage.Declaration) { if (usage is Usage.VariableDeclaration) {
usage.count++ usage.count++
} }
} }
return super.visit(x, ctx) super.visitExpressionStatement(x)
} }
}.accept(root) }.accept(root)
} }
@@ -187,13 +184,13 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
if (usage != null) { if (usage != null) {
val replacement = when (usage) { val replacement = when (usage) {
is Usage.Return -> JsReturn(value).source(x.expression.source) is Usage.Return -> JsReturn(value).source(x.expression.source)
is Usage.Assignment -> { is Usage.VariableAssignment -> {
val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source) val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source)
val statement = JsExpressionStatement(expr) val statement = JsExpressionStatement(expr)
statement.synthetic = usage.target in syntheticNames statement.synthetic = usage.target in syntheticNames
statement statement
} }
is Usage.Declaration -> { is Usage.VariableDeclaration -> {
val statement: JsStatement = if (usage.count > 1) { val statement: JsStatement = if (usage.count > 1) {
val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source) val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source)
val result = JsExpressionStatement(expr) val result = JsExpressionStatement(expr)
@@ -207,7 +204,7 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
} }
statement statement
} }
is Usage.Mutation -> { is Usage.PropertyMutation -> {
JsExpressionStatement(JsAstUtils.assignment(usage.target, value).source(x.expression.source)) JsExpressionStatement(JsAstUtils.assignment(usage.target, value).source(x.expression.source))
} }
} }
@@ -245,8 +242,8 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
private fun generateDeclarations() { private fun generateDeclarations() {
var index = 0 var index = 0
usages.values.asSequence() usages.values.asSequence()
.filter { it is Usage.Declaration && it.count > 1 } .filter { it is Usage.VariableDeclaration && it.count > 1 }
.map { it as Usage.Declaration } .map { it as Usage.VariableDeclaration }
.forEach { .forEach {
val statement = JsAstUtils.newVar(it.target, null) val statement = JsAstUtils.newVar(it.target, null)
statement.synthetic = it.target in syntheticNames statement.synthetic = it.target in syntheticNames
@@ -272,12 +269,12 @@ internal class TemporaryAssignmentElimination(private val root: JsBlock) {
class Return(statement: JsStatement) : Usage(statement) class Return(statement: JsStatement) : Usage(statement)
class Assignment(statement: JsStatement, val target: JsName) : Usage(statement) class VariableAssignment(statement: JsStatement, val target: JsName) : Usage(statement)
class Declaration(statement: JsStatement, val target: JsName) : Usage(statement) { class VariableDeclaration(statement: JsStatement, val target: JsName) : Usage(statement) {
var count = 0 var count = 0
} }
class Mutation(statement: JsStatement, val target: JsExpression) : Usage(statement) class PropertyMutation(statement: JsStatement, val target: JsExpression) : Usage(statement)
} }
} }
@@ -30,23 +30,23 @@ fun collectFunctionReferencesInside(scope: JsNode): List<JsName> =
private fun collectReferencedNames(scope: JsNode): Set<JsName> { private fun collectReferencedNames(scope: JsNode): Set<JsName> {
val references = IdentitySet<JsName>() val references = IdentitySet<JsName>()
object : JsVisitorWithContextImpl() { object : RecursiveJsVisitor() {
override fun visit(x: JsBreak, ctx: JsContext<*>) = false override fun visitBreak(x: JsBreak) { }
override fun visit(x: JsContinue, ctx: JsContext<*>) = false override fun visitContinue(x: JsContinue) { }
override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean { override fun visit(x: JsVars.JsVar) {
val initializer = x.initExpression val initializer = x.initExpression
if (initializer != null) { if (initializer != null) {
accept(initializer) accept(initializer)
} }
return false
} }
override fun endVisit(x: JsNameRef, ctx: JsContext<*>) { override fun visitNameRef(nameRef: JsNameRef) {
val name = x.name super.visitNameRef(nameRef)
val name = nameRef.name
if (name != null) { if (name != null) {
references.add(name) references += name
} }
} }
}.accept(scope) }.accept(scope)
@@ -57,29 +57,28 @@ private fun collectReferencedNames(scope: JsNode): Set<JsName> {
fun collectUsedNames(scope: JsNode): Set<JsName> { fun collectUsedNames(scope: JsNode): Set<JsName> {
val references = IdentitySet<JsName>() val references = IdentitySet<JsName>()
object : JsVisitorWithContextImpl() { object : RecursiveJsVisitor() {
override fun visit(x: JsBreak, ctx: JsContext<*>) = false override fun visitBreak(x: JsBreak) { }
override fun visit(x: JsContinue, ctx: JsContext<*>) = false override fun visitContinue(x: JsContinue) { }
override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean { override fun visit(x: JsVars.JsVar) {
val initializer = x.initExpression val initializer = x.initExpression
if (initializer != null) { if (initializer != null) {
accept(initializer) accept(initializer)
} }
return false
} }
override fun endVisit(x: JsNameRef, ctx: JsContext<*>) { override fun visitNameRef(nameRef: JsNameRef) {
val name = x.name super.visitNameRef(nameRef)
if (name != null && x.qualifier == null) { val name = nameRef.name
if (name != null && nameRef.qualifier == null) {
references.add(name) references.add(name)
} }
} }
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean { override fun visitFunction(x: JsFunction) {
references += x.collectFreeVariables() references += x.collectFreeVariables()
return false
} }
}.accept(scope) }.accept(scope)