Rewrite temporary variable elimination
This commit is contained in:
+357
-139
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.js.inline.clean
|
|||||||
|
|
||||||
import com.google.dart.compiler.backend.js.ast.*
|
import com.google.dart.compiler.backend.js.ast.*
|
||||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||||
import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.collectDefinedNames
|
import org.jetbrains.kotlin.js.inline.util.collectDefinedNames
|
||||||
import org.jetbrains.kotlin.js.inline.util.collectFreeVariables
|
import org.jetbrains.kotlin.js.inline.util.collectFreeVariables
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
@@ -26,196 +25,415 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
|||||||
internal class TemporaryVariableElimination(private val root: JsStatement) {
|
internal class TemporaryVariableElimination(private val root: JsStatement) {
|
||||||
private val definitions = mutableMapOf<JsName, Int>()
|
private val definitions = mutableMapOf<JsName, Int>()
|
||||||
private val usages = mutableMapOf<JsName, Int>()
|
private val usages = mutableMapOf<JsName, Int>()
|
||||||
|
private val inconsistent = mutableSetOf<JsName>()
|
||||||
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
|
private var hasChanges = false
|
||||||
private val namesToProcess = mutableSetOf<JsName>()
|
private val namesToProcess = mutableSetOf<JsName>()
|
||||||
|
private val statementsToRemove = mutableSetOf<JsNode>()
|
||||||
|
private val namesToSubstitute = mutableSetOf<JsName>()
|
||||||
|
|
||||||
fun apply(): Boolean {
|
fun apply(): Boolean {
|
||||||
analyze()
|
analyze()
|
||||||
perform()
|
perform()
|
||||||
|
cleanUp()
|
||||||
return hasChanges
|
return hasChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun analyze() {
|
private fun analyze() {
|
||||||
namesToProcess.addAll(collectDefinedNames(root))
|
namesToProcess += collectDefinedNames(root)
|
||||||
|
|
||||||
object : JsVisitorWithContextImpl() {
|
object : RecursiveJsVisitor() {
|
||||||
val lastAssignedVars = mutableListOf<JsName>()
|
val currentScope = mutableSetOf<JsName>()
|
||||||
|
var localVars = mutableSetOf<JsName>()
|
||||||
|
|
||||||
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean {
|
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||||
val name = x.name
|
|
||||||
if (name != null && x.qualifier == null) {
|
|
||||||
val expr = definedValues[name]
|
|
||||||
useVariable(name)
|
|
||||||
if (lastAssignedVars.lastOrNull() == name && name in namesToProcess) {
|
|
||||||
lastAssignedVars.removeAt(lastAssignedVars.lastIndex)
|
|
||||||
}
|
|
||||||
else if (expr == null || expr.canHaveSideEffect()) {
|
|
||||||
temporary -= name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(x, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsTry, ctx: JsContext<*>): Boolean {
|
|
||||||
lastAssignedVars.clear()
|
|
||||||
return super.visit(x, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsWhile, ctx: JsContext<*>): Boolean {
|
|
||||||
lastAssignedVars.clear()
|
|
||||||
return super.visit(x, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsIf, ctx: JsContext<*>): Boolean {
|
|
||||||
accept(x.ifExpression)
|
|
||||||
lastAssignedVars.clear()
|
|
||||||
|
|
||||||
accept(x.thenStatement)
|
|
||||||
x.elseStatement?.let { accept(it) }
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsInvocation, ctx: JsContext<*>): Boolean {
|
|
||||||
x.arguments.asReversed().forEach { accept(it) }
|
|
||||||
accept(x.qualifier)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsArrayLiteral, ctx: JsContext<*>): Boolean {
|
|
||||||
x.expressions.asReversed().forEach { accept(it) }
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
|
||||||
|
|
||||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
|
||||||
|
|
||||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
|
|
||||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||||
if (assignment != null) {
|
if (assignment != null) {
|
||||||
val (name, value) = assignment
|
val (name, value) = assignment
|
||||||
assignVariable(name, value)
|
if (name in namesToProcess) {
|
||||||
accept(value)
|
|
||||||
if (x.synthetic && name in namesToProcess) {
|
|
||||||
temporary += name
|
|
||||||
}
|
|
||||||
else if (value.canHaveSideEffect()) {
|
|
||||||
lastAssignedVars.clear()
|
|
||||||
}
|
|
||||||
lastAssignedVars += name
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var statement = x
|
|
||||||
val result = accept(x.expression)
|
|
||||||
if (result != x.expression) {
|
|
||||||
statement = JsExpressionStatement(result)
|
|
||||||
ctx.replaceMe(statement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statement.expression.canHaveSideEffect()) {
|
|
||||||
lastAssignedVars.clear()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsBinaryOperation, ctx: JsContext<*>): Boolean {
|
|
||||||
accept(x.arg2)
|
|
||||||
accept(x.arg1)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
|
||||||
for (v in x.vars) {
|
|
||||||
val name = v.name
|
|
||||||
val value = v.initExpression
|
|
||||||
if (value != null) {
|
|
||||||
assignVariable(name, value)
|
assignVariable(name, value)
|
||||||
|
addVar(name)
|
||||||
|
accept(value)
|
||||||
|
if (x.synthetic) {
|
||||||
|
temporary += name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.visitExpressionStatement(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVars(x: JsVars) {
|
||||||
|
for (v in x.vars) {
|
||||||
|
val name = v.name
|
||||||
|
val value = v.initExpression
|
||||||
|
if (value != null && name in namesToProcess) {
|
||||||
|
assignVariable(name, value)
|
||||||
|
addVar(name)
|
||||||
accept(value)
|
accept(value)
|
||||||
if (x.synthetic) {
|
if (x.synthetic) {
|
||||||
temporary += name
|
temporary += name
|
||||||
}
|
}
|
||||||
lastAssignedVars += name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
|
override fun visitNameRef(nameRef: JsNameRef) {
|
||||||
x.collectFreeVariables().forEach { useVariable(it); useVariable(it) }
|
val name = nameRef.name
|
||||||
return super.visit(x, ctx)
|
if (name != null && nameRef.qualifier == null && name in namesToProcess) {
|
||||||
|
useVariable(name)
|
||||||
|
if (name !in currentScope) {
|
||||||
|
inconsistent += name
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.visitNameRef(nameRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(x: JsConditional, ctx: JsContext<*>): Boolean {
|
override fun visitBreak(x: JsBreak) { }
|
||||||
accept(x.testExpression)
|
|
||||||
lastAssignedVars.clear()
|
|
||||||
|
|
||||||
accept(x.thenExpression)
|
override fun visitContinue(x: JsContinue) { }
|
||||||
accept(x.elseExpression)
|
|
||||||
|
|
||||||
return false
|
override fun visitObjectLiteral(x: JsObjectLiteral) {
|
||||||
|
for (initializer in x.propertyInitializers) {
|
||||||
|
accept(initializer.valueExpr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFor(x: JsFor) = withNewScope { super.visitFor(x) }
|
||||||
|
|
||||||
|
override fun visitForIn(x: JsForIn) = withNewScope { super.visitForIn(x) }
|
||||||
|
|
||||||
|
override fun visitWhile(x: JsWhile) = withNewScope { super.visitWhile(x) }
|
||||||
|
|
||||||
|
override fun visitDoWhile(x: JsDoWhile) = withNewScope { super.visitDoWhile(x) }
|
||||||
|
|
||||||
|
override fun visitIf(x: JsIf) {
|
||||||
|
accept(x.ifExpression)
|
||||||
|
withNewScope { accept(x.thenStatement) }
|
||||||
|
x.elseStatement?.let { withNewScope { accept(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitCase(x: JsCase) = withNewScope { super.visitCase(x) }
|
||||||
|
|
||||||
|
override fun visitDefault(x: JsDefault) = withNewScope { super.visitDefault(x) }
|
||||||
|
|
||||||
|
override fun visitCatch(x: JsCatch) = withNewScope { super.visitCatch(x) }
|
||||||
|
|
||||||
|
override fun visitFunction(x: JsFunction) {
|
||||||
|
for (freeVar in x.collectFreeVariables()) {
|
||||||
|
useVariable(freeVar)
|
||||||
|
useVariable(freeVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T> withNewScope(block: () -> T): T {
|
||||||
|
val localVarsBackup = localVars
|
||||||
|
try {
|
||||||
|
localVars = mutableSetOf()
|
||||||
|
return block()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
currentScope -= localVars
|
||||||
|
localVars = localVarsBackup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addVar(name: JsName) {
|
||||||
|
currentScope += name
|
||||||
|
localVars.add(name)
|
||||||
}
|
}
|
||||||
}.accept(root)
|
}.accept(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perform() {
|
private fun perform() {
|
||||||
object : JsVisitorWithContextImpl() {
|
object : RecursiveJsVisitor() {
|
||||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
|
val lastAssignedVars = mutableListOf<Pair<JsName, JsNode>>()
|
||||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
var lastProperlyUsedIndex = -1
|
||||||
|
var firstUsedIndex = 0
|
||||||
|
var nextExpectedIndex = -1
|
||||||
|
var sideEffectOccurred = false
|
||||||
|
|
||||||
|
override fun visitNameRef(nameRef: JsNameRef) {
|
||||||
|
val name = nameRef.name
|
||||||
|
if (name != null && nameRef.qualifier == null && shouldConsiderTemporary(name)) {
|
||||||
|
val index = lastAssignedVars.indexOfFirst { it.first == name }
|
||||||
|
if (index >= 0) {
|
||||||
|
if (sideEffectOccurred) {
|
||||||
|
lastProperlyUsedIndex = -1
|
||||||
|
}
|
||||||
|
if (index == nextExpectedIndex) {
|
||||||
|
++nextExpectedIndex
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lastProperlyUsedIndex = index
|
||||||
|
nextExpectedIndex = index + 1
|
||||||
|
}
|
||||||
|
firstUsedIndex = Math.min(firstUsedIndex, index)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.visitNameRef(nameRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitInvocation(invocation: JsInvocation) {
|
||||||
|
super.visitInvocation(invocation)
|
||||||
|
sideEffectOccurred = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitNew(x: JsNew) {
|
||||||
|
super.visitNew(x)
|
||||||
|
sideEffectOccurred = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitPrefixOperation(x: JsPrefixOperation) {
|
||||||
|
super.visitPrefixOperation(x)
|
||||||
|
when (x.operator) {
|
||||||
|
JsUnaryOperator.INC, JsUnaryOperator.DEC -> sideEffectOccurred = true
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitPostfixOperation(x: JsPostfixOperation) {
|
||||||
|
super.visitPostfixOperation(x)
|
||||||
|
when (x.operator) {
|
||||||
|
JsUnaryOperator.INC, JsUnaryOperator.DEC -> sideEffectOccurred = true
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBinaryExpression(x: JsBinaryOperation) {
|
||||||
|
super.visitBinaryExpression(x)
|
||||||
|
if (x.operator == JsBinaryOperator.ASG) {
|
||||||
|
sideEffectOccurred = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitArray(x: JsArrayLiteral) {
|
||||||
|
super.visitArray(x)
|
||||||
|
sideEffectOccurred = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||||
|
val expression = x.expression
|
||||||
|
val assignment = JsAstUtils.decomposeAssignmentToVariable(expression)
|
||||||
if (assignment != null) {
|
if (assignment != null) {
|
||||||
if (shouldConsiderTemporary(assignment.first)) {
|
val (name, value) = assignment
|
||||||
hasChanges = true
|
if (shouldConsiderTemporary(name)) {
|
||||||
ctx.removeMe()
|
handleTopLevel(value, false)
|
||||||
return false
|
if (isTrivial(value) && name !in inconsistent) {
|
||||||
|
statementsToRemove += x
|
||||||
|
namesToSubstitute += name
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lastAssignedVars += Pair(name, x)
|
||||||
|
sideEffectOccurred = false
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
handleTopLevel(expression)
|
||||||
return super.visit(x, ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
override fun visitIf(x: JsIf) {
|
||||||
val filteredVars = x.vars.filter { it.initExpression == null || !shouldConsiderTemporary(it.name) }
|
handleTopLevel(x.ifExpression)
|
||||||
if (x.vars.size != filteredVars.size) {
|
flush()
|
||||||
hasChanges = true
|
accept(x.thenStatement)
|
||||||
x.vars.clear()
|
flush()
|
||||||
x.vars.addAll(filteredVars)
|
x.elseStatement?.let { accept(it); flush() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitConditional(x: JsConditional) {
|
||||||
|
accept(x.testExpression)
|
||||||
|
|
||||||
|
val lastAssignedVarsBackup = lastAssignedVars.toMutableList()
|
||||||
|
lastAssignedVars.clear()
|
||||||
|
accept(x.thenExpression)
|
||||||
|
accept(x.elseExpression)
|
||||||
|
lastAssignedVars.clear()
|
||||||
|
lastAssignedVars += lastAssignedVarsBackup
|
||||||
|
|
||||||
|
sideEffectOccurred = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitReturn(x: JsReturn) {
|
||||||
|
x.expression?.let { handleTopLevel(it) }
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitThrow(x: JsThrow) {
|
||||||
|
handleTopLevel(x.expression)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(x: JsSwitch) {
|
||||||
|
handleTopLevel(x.expression)
|
||||||
|
flush()
|
||||||
|
x.cases.forEach { accept(it); flush() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitObjectLiteral(x: JsObjectLiteral) {
|
||||||
|
for (initializer in x.propertyInitializers) {
|
||||||
|
accept(initializer.valueExpr)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitWhile(x: JsWhile) {
|
||||||
|
flush()
|
||||||
|
super.visitWhile(x)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDoWhile(x: JsDoWhile) {
|
||||||
|
flush()
|
||||||
|
super.visitDoWhile(x)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitForIn(x: JsForIn) {
|
||||||
|
handleTopLevel(x.objectExpression)
|
||||||
|
flush()
|
||||||
|
accept(x.body)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFor(x: JsFor) {
|
||||||
|
x.initVars?.let { accept(it) }
|
||||||
|
x.initExpression?.let { handleTopLevel(it) }
|
||||||
|
|
||||||
|
flush()
|
||||||
|
|
||||||
|
x.condition?.let { accept(it) }
|
||||||
|
x.incrementExpression?.let { accept(it) }
|
||||||
|
|
||||||
|
flush()
|
||||||
|
accept(x.body)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitTry(x: JsTry) {
|
||||||
|
flush()
|
||||||
|
super.visitTry(x)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitCatch(x: JsCatch) {
|
||||||
|
flush()
|
||||||
|
super.visitCatch(x)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitLabel(x: JsLabel) {
|
||||||
|
flush()
|
||||||
|
super.visitLabel(x)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVars(x: JsVars) {
|
||||||
|
for (v in x.vars) {
|
||||||
|
val initializer = v.initExpression
|
||||||
|
if (initializer != null) {
|
||||||
|
val name = v.name
|
||||||
|
if (shouldConsiderTemporary(name)) {
|
||||||
|
handleTopLevel(initializer, false)
|
||||||
|
if (isTrivial(initializer) && name !in inconsistent) {
|
||||||
|
statementsToRemove += v
|
||||||
|
namesToSubstitute += name
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lastAssignedVars += Pair(name, v)
|
||||||
|
sideEffectOccurred = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleTopLevel(initializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunction(x: JsFunction) { }
|
||||||
|
|
||||||
|
override fun visitBreak(x: JsBreak) { }
|
||||||
|
|
||||||
|
override fun visitContinue(x: JsContinue) { }
|
||||||
|
|
||||||
|
private fun flush() {
|
||||||
|
lastAssignedVars.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTopLevel(expression: JsExpression, respectSideEffects: Boolean = true) {
|
||||||
|
lastProperlyUsedIndex = -1
|
||||||
|
firstUsedIndex = lastAssignedVars.size
|
||||||
|
nextExpectedIndex = -1
|
||||||
|
sideEffectOccurred = false
|
||||||
|
|
||||||
|
accept(expression)
|
||||||
|
|
||||||
|
if (lastProperlyUsedIndex >= 0 && nextExpectedIndex == lastAssignedVars.size) {
|
||||||
|
lastAssignedVars.asSequence()
|
||||||
|
.drop(lastProperlyUsedIndex)
|
||||||
|
.forEach {
|
||||||
|
val (name, statement) = it
|
||||||
|
statementsToRemove += statement
|
||||||
|
namesToSubstitute += name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (respectSideEffects && sideEffectOccurred) {
|
||||||
|
lastAssignedVars.clear()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lastAssignedVars.subList(firstUsedIndex, lastAssignedVars.size).clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.accept(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanUp() {
|
||||||
|
object : JsVisitorWithContextImpl() {
|
||||||
|
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||||
|
x.vars.removeAll { it in statementsToRemove }
|
||||||
if (x.vars.isEmpty()) {
|
if (x.vars.isEmpty()) {
|
||||||
ctx.removeMe()
|
ctx.removeMe()
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(x, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
|
||||||
val name = x.name
|
|
||||||
if (x.qualifier == null && name != null && shouldConsiderTemporary(name)) {
|
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
val newExpr = definedValues[name] ?: JsLiteral.UNDEFINED
|
|
||||||
ctx.replaceMe(accept(newExpr))
|
|
||||||
usages[name] = usages[name]!! - 1
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return super.visit(x, ctx)
|
return super.visit(x, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
|
||||||
|
if (x in statementsToRemove) {
|
||||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
ctx.removeMe()
|
||||||
|
hasChanges = true
|
||||||
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
|
return false
|
||||||
val returnValue = x.expression
|
|
||||||
if (returnValue is JsNameRef) {
|
|
||||||
val name = returnValue.name
|
|
||||||
if (returnValue.qualifier == null && name != null && name in temporary && definitions[name] ?: 0 == 0) {
|
|
||||||
x.expression = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.visit(x, ctx)
|
return super.visit(x, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(x: JsObjectLiteral, ctx: JsContext<*>): Boolean {
|
||||||
|
for (initializer in x.propertyInitializers) {
|
||||||
|
accept(initializer.valueExpr)
|
||||||
|
}
|
||||||
|
return super.visit(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(x: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
||||||
|
val name = x.name
|
||||||
|
if (name != null && x.qualifier == null && name in namesToSubstitute) {
|
||||||
|
val replacement = accept(definedValues[name]!!)
|
||||||
|
ctx.replaceMe(replacement)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return super.visit(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(x: JsFunction, ctx: JsContext<*>) = false
|
||||||
|
|
||||||
|
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
||||||
|
|
||||||
|
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
||||||
}.accept(root)
|
}.accept(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import java.util.*
|
|||||||
fun collectFunctionReferencesInside(scope: JsNode): List<JsName> =
|
fun collectFunctionReferencesInside(scope: JsNode): List<JsName> =
|
||||||
collectReferencedNames(scope).filter { it.staticRef is JsFunction }
|
collectReferencedNames(scope).filter { it.staticRef is JsFunction }
|
||||||
|
|
||||||
fun collectReferencedNames(scope: JsNode): Set<JsName> {
|
private fun collectReferencedNames(scope: JsNode): Set<JsName> {
|
||||||
val references = IdentitySet<JsName>()
|
val references = IdentitySet<JsName>()
|
||||||
|
|
||||||
object : JsVisitorWithContextImpl() {
|
object : JsVisitorWithContextImpl() {
|
||||||
@@ -98,13 +98,19 @@ fun collectDefinedNames(scope: JsNode): Set<JsName> {
|
|||||||
addNameIfNeeded(x.name)
|
addNameIfNeeded(x.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitFunction(x: JsFunction) {
|
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||||
val name = x.name
|
val expression = x.expression
|
||||||
if (name != null) {
|
if (expression is JsFunction) {
|
||||||
addNameIfNeeded(x.name)
|
val name = expression.name
|
||||||
|
if (name != null) {
|
||||||
|
addNameIfNeeded(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
super.visitExpressionStatement(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitFunction(x: JsFunction) { }
|
||||||
|
|
||||||
private fun addNameIfNeeded(name: JsName) {
|
private fun addNameIfNeeded(name: JsName) {
|
||||||
val ident = name.ident
|
val ident = name.ident
|
||||||
val nameCollected = names[ident]
|
val nameCollected = names[ident]
|
||||||
|
|||||||
+8
@@ -24,4 +24,12 @@ class TemporaryVariableEliminationTest : BasicOptimizerTest("temporary-variable"
|
|||||||
@Test fun assignment() = box()
|
@Test fun assignment() = box()
|
||||||
|
|
||||||
@Test fun skipsGlobalDeclarations() = box()
|
@Test fun skipsGlobalDeclarations() = box()
|
||||||
|
|
||||||
|
@Test fun methodCall() = box()
|
||||||
|
|
||||||
|
@Test fun ifBranch() = box()
|
||||||
|
|
||||||
|
@Test fun tryCatch() = box()
|
||||||
|
|
||||||
|
@Test fun nonSideEffect() = box()
|
||||||
}
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
function test1(n) {
|
||||||
|
var $tmp;
|
||||||
|
if (n > 0) {
|
||||||
|
$tmp = 23;
|
||||||
|
}
|
||||||
|
return typeof $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
var result = test1(5);
|
||||||
|
if (result != "number") return "fail1: " + result;
|
||||||
|
|
||||||
|
result = test1(-5);
|
||||||
|
if (result != "undefined") return "fail2: " + result;
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
function test1(n) {
|
||||||
|
var $tmp;
|
||||||
|
if (n > 0) {
|
||||||
|
$tmp = 23;
|
||||||
|
}
|
||||||
|
return typeof $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
var result = test1(5);
|
||||||
|
if (result != "number") return "fail1: " + result;
|
||||||
|
|
||||||
|
result = test1(-5);
|
||||||
|
if (result != "undefined") return "fail2: " + result;
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
var log = "";
|
||||||
|
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
log = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function foo(n) {
|
||||||
|
log += "{" + n + "}";
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function test1() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
return foo(foo(1) + foo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function test2() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
|
||||||
|
return foo($tmp1 + $tmp2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test3() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
return foo(foo(1) + foo(2) + foo(foo(3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function test4() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
var $tmp3 = foo(3);
|
||||||
|
|
||||||
|
return foo(foo($tmp1) + $tmp2 + $tmp3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test5() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
var $tmp3 = foo(3);
|
||||||
|
foo(4);
|
||||||
|
|
||||||
|
return $tmp1 + $tmp2 + $tmp3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
var result = test1();
|
||||||
|
if (result != 3) return "fail1a: " + result;
|
||||||
|
if (log != "{1}{2}{3}") return "fail1b: " + log;
|
||||||
|
|
||||||
|
result = test2();
|
||||||
|
if (result != 3) return "fail2a: " + result;
|
||||||
|
if (log != "{2}{1}{3}") return "fail2b: " + log;
|
||||||
|
|
||||||
|
result = test3();
|
||||||
|
if (result != 6) return "fail3a: " + result;
|
||||||
|
if (log != "{1}{2}{3}{3}{6}") return "fail3b: " + log;
|
||||||
|
|
||||||
|
result = test4();
|
||||||
|
if (result != 6) return "fail4a: " + result;
|
||||||
|
if (log != "{1}{2}{3}{1}{6}") return "fail4b: " + log;
|
||||||
|
|
||||||
|
result = test5();
|
||||||
|
if (result != 6) return "fail5a: " + result;
|
||||||
|
if (log != "{1}{2}{3}{4}") return "fail5b: " + log;
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
var log = "";
|
||||||
|
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
log = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function foo(n) {
|
||||||
|
log += "{" + n + "}";
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function test1() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
|
||||||
|
return foo($tmp1 + $tmp2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test2() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
|
||||||
|
return foo($tmp1 + $tmp2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test3() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
var $tmp3 = foo(3);
|
||||||
|
|
||||||
|
return foo($tmp1 + $tmp2 + foo($tmp3));
|
||||||
|
}
|
||||||
|
|
||||||
|
function test4() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
var $tmp3 = foo(3);
|
||||||
|
|
||||||
|
return foo(foo($tmp1) + $tmp2 + $tmp3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test5() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
var $tmp1 = foo(1);
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
var $tmp3 = foo(3);
|
||||||
|
foo(4);
|
||||||
|
|
||||||
|
return $tmp1 + $tmp2 + $tmp3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
var result = test1();
|
||||||
|
if (result != 3) return "fail1a: " + result;
|
||||||
|
if (log != "{1}{2}{3}") return "fail1b: " + log;
|
||||||
|
|
||||||
|
result = test2();
|
||||||
|
if (result != 3) return "fail2a: " + result;
|
||||||
|
if (log != "{2}{1}{3}") return "fail2b: " + log;
|
||||||
|
|
||||||
|
result = test3();
|
||||||
|
if (result != 6) return "fail3a: " + result;
|
||||||
|
if (log != "{1}{2}{3}{3}{6}") return "fail3b: " + log;
|
||||||
|
|
||||||
|
result = test4();
|
||||||
|
if (result != 6) return "fail4a: " + result;
|
||||||
|
if (log != "{1}{2}{3}{1}{6}") return "fail4b: " + log;
|
||||||
|
|
||||||
|
result = test5();
|
||||||
|
if (result != 6) return "fail5a: " + result;
|
||||||
|
if (log != "{1}{2}{3}{4}") return "fail5b: " + log;
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
function foo(x) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
var result = foo(2) + 1;
|
||||||
|
if (result != 3) return "fail: " + result;
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
function foo(x) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
var $tmp1 = 1;
|
||||||
|
var $tmp2 = foo(2);
|
||||||
|
|
||||||
|
var result = $tmp2 + $tmp1;
|
||||||
|
if (result != 3) return "fail: " + result;
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
function foo() {
|
||||||
|
throw "foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
function bar() {
|
||||||
|
var $tmp = foo();
|
||||||
|
try {
|
||||||
|
return "result: " + $tmp;
|
||||||
|
} catch (e) {
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
try {
|
||||||
|
bar()
|
||||||
|
} catch (e) {
|
||||||
|
if (e == "foo") {
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
return "Exception expected";
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
function foo() {
|
||||||
|
throw "foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
function bar() {
|
||||||
|
var $tmp = foo();
|
||||||
|
try {
|
||||||
|
return "result: " + $tmp;
|
||||||
|
} catch (e) {
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function box() {
|
||||||
|
try {
|
||||||
|
bar()
|
||||||
|
} catch (e) {
|
||||||
|
if (e == "foo") {
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
return "Exception expected";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user