Rewrite temporary variable elimination

This commit is contained in:
Alexey Andreev
2016-03-23 11:54:58 +03:00
parent 8709289b0a
commit cfdce8eaca
11 changed files with 642 additions and 144 deletions
@@ -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.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.collectFreeVariables
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) {
private val definitions = mutableMapOf<JsName, Int>()
private val usages = mutableMapOf<JsName, Int>()
private val inconsistent = mutableSetOf<JsName>()
private val definedValues = mutableMapOf<JsName, JsExpression>()
private val temporary = mutableSetOf<JsName>()
private var hasChanges = false
private val namesToProcess = mutableSetOf<JsName>()
private val statementsToRemove = mutableSetOf<JsNode>()
private val namesToSubstitute = mutableSetOf<JsName>()
fun apply(): Boolean {
analyze()
perform()
cleanUp()
return hasChanges
}
private fun analyze() {
namesToProcess.addAll(collectDefinedNames(root))
namesToProcess += collectDefinedNames(root)
object : JsVisitorWithContextImpl() {
val lastAssignedVars = mutableListOf<JsName>()
object : RecursiveJsVisitor() {
val currentScope = mutableSetOf<JsName>()
var localVars = mutableSetOf<JsName>()
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean {
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 {
override fun visitExpressionStatement(x: JsExpressionStatement) {
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
if (assignment != null) {
val (name, value) = assignment
assignVariable(name, value)
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) {
if (name in namesToProcess) {
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)
if (x.synthetic) {
temporary += name
}
lastAssignedVars += name
}
}
return false
}
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
x.collectFreeVariables().forEach { useVariable(it); useVariable(it) }
return super.visit(x, ctx)
override fun visitNameRef(nameRef: JsNameRef) {
val name = nameRef.name
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 {
accept(x.testExpression)
lastAssignedVars.clear()
override fun visitBreak(x: JsBreak) { }
accept(x.thenExpression)
accept(x.elseExpression)
override fun visitContinue(x: JsContinue) { }
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)
}
private fun perform() {
object : JsVisitorWithContextImpl() {
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
object : RecursiveJsVisitor() {
val lastAssignedVars = mutableListOf<Pair<JsName, JsNode>>()
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 (shouldConsiderTemporary(assignment.first)) {
hasChanges = true
ctx.removeMe()
return false
val (name, value) = assignment
if (shouldConsiderTemporary(name)) {
handleTopLevel(value, false)
if (isTrivial(value) && name !in inconsistent) {
statementsToRemove += x
namesToSubstitute += name
}
else {
lastAssignedVars += Pair(name, x)
sideEffectOccurred = false
}
return
}
}
return super.visit(x, ctx)
handleTopLevel(expression)
}
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
val filteredVars = x.vars.filter { it.initExpression == null || !shouldConsiderTemporary(it.name) }
if (x.vars.size != filteredVars.size) {
hasChanges = true
x.vars.clear()
x.vars.addAll(filteredVars)
override fun visitIf(x: JsIf) {
handleTopLevel(x.ifExpression)
flush()
accept(x.thenStatement)
flush()
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()) {
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
val newExpr = definedValues[name] ?: JsLiteral.UNDEFINED
ctx.replaceMe(accept(newExpr))
usages[name] = usages[name]!! - 1
return false
}
return super.visit(x, ctx)
}
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
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
}
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
if (x in statementsToRemove) {
ctx.removeMe()
hasChanges = true
return false
}
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)
}
@@ -27,7 +27,7 @@ import java.util.*
fun collectFunctionReferencesInside(scope: JsNode): List<JsName> =
collectReferencedNames(scope).filter { it.staticRef is JsFunction }
fun collectReferencedNames(scope: JsNode): Set<JsName> {
private fun collectReferencedNames(scope: JsNode): Set<JsName> {
val references = IdentitySet<JsName>()
object : JsVisitorWithContextImpl() {
@@ -98,13 +98,19 @@ fun collectDefinedNames(scope: JsNode): Set<JsName> {
addNameIfNeeded(x.name)
}
override fun visitFunction(x: JsFunction) {
val name = x.name
if (name != null) {
addNameIfNeeded(x.name)
override fun visitExpressionStatement(x: JsExpressionStatement) {
val expression = x.expression
if (expression is JsFunction) {
val name = expression.name
if (name != null) {
addNameIfNeeded(name)
}
}
super.visitExpressionStatement(x)
}
override fun visitFunction(x: JsFunction) { }
private fun addNameIfNeeded(name: JsName) {
val ident = name.ident
val nameCollected = names[ident]
@@ -24,4 +24,12 @@ class TemporaryVariableEliminationTest : BasicOptimizerTest("temporary-variable"
@Test fun assignment() = box()
@Test fun skipsGlobalDeclarations() = box()
@Test fun methodCall() = box()
@Test fun ifBranch() = box()
@Test fun tryCatch() = box()
@Test fun nonSideEffect() = box()
}
@@ -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";
}
@@ -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";
}
@@ -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";
}
@@ -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";
}
@@ -0,0 +1,10 @@
function foo(x) {
return x;
}
function box() {
var result = foo(2) + 1;
if (result != 3) return "fail: " + result;
return "OK";
}
@@ -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";
}
@@ -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";
}
@@ -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";
}