[JS IR] Drop TemporaryAssignmentElimination optimization
Enable TemporaryAssignment tests
This commit is contained in:
committed by
Space Team
parent
a79282cec1
commit
25f7b81d51
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||
|
||||
class FunctionPostProcessor(val root: JsFunction) {
|
||||
val optimizations = listOf(
|
||||
//{ TemporaryAssignmentElimination(root.body).apply() },
|
||||
{ RedundantLabelRemoval(root.body).apply() },
|
||||
{ EmptyStatementElimination(root.body).apply() },
|
||||
{ WhileConditionFolding(root.body).apply() },
|
||||
@@ -44,4 +43,4 @@ class FunctionPostProcessor(val root: JsFunction) {
|
||||
}
|
||||
} while (hasChanges)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-285
@@ -1,285 +0,0 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.backend.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
|
||||
|
||||
internal class TemporaryAssignmentElimination(private val root: JsBlock) {
|
||||
private val referenceCount = mutableMapOf<JsName, Int>()
|
||||
|
||||
// We say "usage" about special kind of a reference to a temporary variable in the following cases:
|
||||
// someVar = $tmp;
|
||||
// var someVar = $tmp;
|
||||
// someObj.prop = $tmp;
|
||||
// return $tmp;
|
||||
private val usages = mutableMapOf<JsName, Usage>()
|
||||
private val statementsToRemove = mutableSetOf<JsStatement>()
|
||||
private val usageSequences = mutableMapOf<JsName, UsageSequence?>()
|
||||
private val syntheticNames = mutableSetOf<JsName>()
|
||||
private var hasChanges = false
|
||||
private val namesToProcess = mutableSetOf<JsName>()
|
||||
|
||||
fun apply(): Boolean {
|
||||
analyze()
|
||||
calculateDeclarations()
|
||||
process()
|
||||
generateDeclarations()
|
||||
return hasChanges
|
||||
}
|
||||
|
||||
private fun analyze() {
|
||||
namesToProcess.addAll(collectDefinedNames(root))
|
||||
|
||||
object : RecursiveJsVisitor() {
|
||||
override fun visitReturn(x: JsReturn) {
|
||||
val returnExpr = x.expression
|
||||
if (returnExpr != null) {
|
||||
tryRecord(returnExpr, Usage.Return(x))
|
||||
}
|
||||
super.visitReturn(x)
|
||||
}
|
||||
|
||||
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||
val variableAssignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (variableAssignment != null) {
|
||||
val (name, value) = variableAssignment
|
||||
val usage = Usage.VariableAssignment(x, name)
|
||||
if (x.synthetic) {
|
||||
syntheticNames += name
|
||||
}
|
||||
tryRecord(value, usage)
|
||||
accept(value)
|
||||
// Don't visit LHS, since it's already treated as a temporary variable
|
||||
return
|
||||
}
|
||||
|
||||
val propertyMutation = JsAstUtils.decomposeAssignment(x.expression)
|
||||
if (propertyMutation != null) {
|
||||
val (target, value) = propertyMutation
|
||||
if (!target.canHaveSideEffect(namesToProcess)) {
|
||||
val usage = Usage.PropertyMutation(x, target)
|
||||
tryRecord(value, usage)
|
||||
accept(value)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitExpressionStatement(x)
|
||||
}
|
||||
|
||||
override fun visitVars(x: JsVars) {
|
||||
// TODO: generalize for multiple declarations per one statement
|
||||
if (x.vars.size == 1) {
|
||||
val declaration = x.vars[0]
|
||||
val initExpression = declaration.initExpression
|
||||
if (initExpression != null) {
|
||||
tryRecord(initExpression, Usage.VariableDeclaration(x, declaration.name))
|
||||
}
|
||||
// TODO: generalize for case when single JsVar is synthetic
|
||||
if (x.synthetic) {
|
||||
syntheticNames += declaration.name
|
||||
}
|
||||
}
|
||||
|
||||
x.vars.forEach { v -> v?.initExpression?.let { accept(it) } }
|
||||
}
|
||||
|
||||
override fun visitNameRef(nameRef: JsNameRef) {
|
||||
val name = nameRef.name
|
||||
if (name != null && nameRef.qualifier == null) {
|
||||
use(name)
|
||||
return
|
||||
}
|
||||
return super.visitNameRef(nameRef)
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
override fun visitBreak(x: JsBreak) { }
|
||||
|
||||
override fun visitContinue(x: JsContinue) { }
|
||||
|
||||
override fun visitFor(x: JsFor) {
|
||||
x.initVars?.let { it.vars.forEach { it.initExpression?.let { accept(it) } } }
|
||||
x.initExpression?.let { accept(it) }
|
||||
x.condition?.let { accept(it) }
|
||||
x.body?.let { accept(it) }
|
||||
x.incrementExpression?.let { accept(it) }
|
||||
}
|
||||
}.accept(root)
|
||||
|
||||
usages.keys.retainAll(syntheticNames)
|
||||
}
|
||||
|
||||
private fun getUsageSequence(name: JsName): UsageSequence? {
|
||||
return usageSequences.getOrPut(name) {
|
||||
if (referenceCount[name] != 1) return null
|
||||
|
||||
val usage = usages[name]
|
||||
val mappedUsage: UsageSequence? = when (usage) {
|
||||
is Usage.VariableAssignment -> UsageSequence(usage, getUsageSequence(usage.target))
|
||||
is Usage.VariableDeclaration -> UsageSequence(usage, getUsageSequence(usage.target))
|
||||
null -> null
|
||||
else -> UsageSequence(usage, null)
|
||||
}
|
||||
|
||||
mappedUsage
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateDeclarations() {
|
||||
usages.keys.forEach { getUsageSequence(it) }
|
||||
|
||||
object : RecursiveJsVisitor() {
|
||||
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (assignment != null) {
|
||||
val usage = getUsageSequence(assignment.first)?.lastUsage()
|
||||
if (usage is Usage.VariableDeclaration) {
|
||||
usage.count++
|
||||
}
|
||||
}
|
||||
super.visitExpressionStatement(x)
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun process() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
|
||||
if (x in statementsToRemove) {
|
||||
hasChanges = true
|
||||
ctx.removeMe()
|
||||
return false
|
||||
}
|
||||
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (assignment != null) {
|
||||
val (name, value) = assignment
|
||||
val usageSequence = getUsageSequence(name)
|
||||
if (usageSequence != null) {
|
||||
val usage = usageSequence.lastUsage()
|
||||
val replacement = when (usage) {
|
||||
is Usage.Return -> JsReturn(value).apply { source(x.expression.source) }
|
||||
is Usage.VariableAssignment -> {
|
||||
val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source)
|
||||
val statement = JsExpressionStatement(expr)
|
||||
statement.synthetic = usage.target in syntheticNames
|
||||
statement
|
||||
}
|
||||
is Usage.VariableDeclaration -> {
|
||||
val statement: JsStatement = if (usage.count > 1) {
|
||||
val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source)
|
||||
val result = JsExpressionStatement(expr)
|
||||
result.synthetic = usage.target in syntheticNames
|
||||
result
|
||||
}
|
||||
else {
|
||||
val declaration = JsAstUtils.newVar(usage.target, value)
|
||||
declaration.source(x.expression.source)
|
||||
declaration.synthetic = usage.target in syntheticNames
|
||||
declaration
|
||||
}
|
||||
statement
|
||||
}
|
||||
is Usage.PropertyMutation -> {
|
||||
JsExpressionStatement(JsAstUtils.assignment(usage.target, value).source(x.expression.source))
|
||||
}
|
||||
}
|
||||
hasChanges = true
|
||||
ctx.replaceMe(replacement)
|
||||
statementsToRemove += usageSequence.collectStatements()
|
||||
return false
|
||||
}
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
|
||||
if (x in statementsToRemove) {
|
||||
hasChanges = true
|
||||
ctx.removeMe()
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
if (x in statementsToRemove) {
|
||||
hasChanges = true
|
||||
ctx.removeMe()
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsFunction, ctx: JsContext<*>) = false
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun generateDeclarations() {
|
||||
var index = 0
|
||||
usages.values.asSequence()
|
||||
.filter { it is Usage.VariableDeclaration && it.count > 1 }
|
||||
.map { it as Usage.VariableDeclaration }
|
||||
.forEach {
|
||||
val statement = JsAstUtils.newVar(it.target, null)
|
||||
statement.synthetic = it.target in syntheticNames
|
||||
root.statements.add(index++, statement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryRecord(expr: JsExpression, usage: Usage): Boolean {
|
||||
if (expr !is JsNameRef) return false
|
||||
val name = expr.name ?: return false
|
||||
if (name !in namesToProcess) return false
|
||||
|
||||
usages[name] = usage
|
||||
return true
|
||||
}
|
||||
|
||||
private fun use(name: JsName) {
|
||||
referenceCount[name] = 1 + (referenceCount[name] ?: 0)
|
||||
}
|
||||
|
||||
private sealed class Usage(val statement: JsStatement) {
|
||||
class Return(statement: JsStatement) : Usage(statement)
|
||||
|
||||
class VariableAssignment(statement: JsStatement, val target: JsName) : Usage(statement)
|
||||
|
||||
class VariableDeclaration(statement: JsStatement, val target: JsName) : Usage(statement) {
|
||||
var count = 0
|
||||
}
|
||||
|
||||
class PropertyMutation(statement: JsStatement, val target: JsExpression) : Usage(statement)
|
||||
}
|
||||
|
||||
private class UsageSequence(val value: Usage, val next: UsageSequence?) {
|
||||
fun collectStatements() = generateSequence(this) { it.next }.map { it.value.statement }
|
||||
|
||||
fun lastUsage() = generateSequence(this) { it.next }.last().value
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -16,11 +16,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.js.testOld.optimizer
|
||||
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
@Ignore
|
||||
class TemporaryAssignmentEliminationTest : BasicOptimizerTest("temporary-assignment") {
|
||||
class TemporaryAssignmentTest : BasicOptimizerTest("temporary-assignment") {
|
||||
@Test fun assignment() = box()
|
||||
|
||||
@Test fun returnStatement() = box()
|
||||
@@ -40,4 +38,4 @@ class TemporaryAssignmentEliminationTest : BasicOptimizerTest("temporary-assignm
|
||||
@Test fun forInitVariables() = box()
|
||||
|
||||
@Test fun forInitExpressionPreventsOptimization() = box()
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -1,11 +1,12 @@
|
||||
function test(n) {
|
||||
var result;
|
||||
var $tmp;
|
||||
if (n >= 0) {
|
||||
result = n;
|
||||
}
|
||||
else {
|
||||
result = -n;
|
||||
$tmp = n;
|
||||
} else {
|
||||
$tmp = -n;
|
||||
}
|
||||
result = $tmp;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -17,4 +18,4 @@ function box() {
|
||||
if (result != 20) return "fail2: " + result;
|
||||
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
function test(n) {
|
||||
var result;
|
||||
var $tmp;
|
||||
if (n >= 0) {
|
||||
result = n;
|
||||
}
|
||||
else {
|
||||
result = -n;
|
||||
$tmp = n;
|
||||
} else {
|
||||
$tmp = -n;
|
||||
}
|
||||
var result = $tmp;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@ function box() {
|
||||
if (result != 20) return "fail2: " + result;
|
||||
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -3,17 +3,19 @@ function f(x) {
|
||||
}
|
||||
|
||||
function box() {
|
||||
var result1, result2;
|
||||
var result1, result2, $a, $b;
|
||||
|
||||
if (f(true)) {
|
||||
result1 = "1";
|
||||
$a = "1";
|
||||
}
|
||||
result1 = $a;
|
||||
if (result1 !== "1") return "fail1: " + result1;
|
||||
|
||||
if (f(false)) {
|
||||
result2 = "1";
|
||||
$b = "1";
|
||||
}
|
||||
result2 = $b;
|
||||
if (result2 !== void 0) return "fail2: " + result2;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -1,10 +1,11 @@
|
||||
function test(n) {
|
||||
var $tmp;
|
||||
if (n >= 0) {
|
||||
return n;
|
||||
}
|
||||
else {
|
||||
return -n;
|
||||
$tmp = n;
|
||||
} else {
|
||||
$tmp = -n;
|
||||
}
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
function box() {
|
||||
@@ -15,4 +16,4 @@ function box() {
|
||||
if (result != 20) return "fail2: " + result;
|
||||
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+5
-3
@@ -1,10 +1,12 @@
|
||||
function test(param) {
|
||||
var $a;
|
||||
var result;
|
||||
if (param > 0) {
|
||||
result = param;
|
||||
$a = param;
|
||||
} else {
|
||||
result = -param;
|
||||
$a = -param;
|
||||
}
|
||||
result = $a;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -16,4 +18,4 @@ function box() {
|
||||
if (result != 20) return "fail2: " + result;
|
||||
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
+5
-9
@@ -3,22 +3,19 @@ function f(x) {
|
||||
}
|
||||
|
||||
function testRegular() {
|
||||
var result;
|
||||
result = f("testRegular");
|
||||
var result = f("testRegular");
|
||||
return result;
|
||||
}
|
||||
|
||||
function testIrregular() {
|
||||
var result;
|
||||
result = f("testIrregular");
|
||||
var result = f("testIrregular");
|
||||
return result;
|
||||
}
|
||||
|
||||
function testDoubleUse1() {
|
||||
var result;
|
||||
var $b;
|
||||
$b = f("testDoubleUse1");
|
||||
result = $b;
|
||||
var result = $b;
|
||||
f($b);
|
||||
return result;
|
||||
}
|
||||
@@ -32,10 +29,9 @@ function testDoubleUse2() {
|
||||
}
|
||||
|
||||
function testDoubleUse3() {
|
||||
var result;
|
||||
var $a;
|
||||
$a = f("testDoubleUse3");
|
||||
result = $a;
|
||||
var result = $a;
|
||||
f($a);
|
||||
return result;
|
||||
}
|
||||
@@ -68,4 +64,4 @@ function box() {
|
||||
if (result != "testCircular") return "failCircular: " + result;
|
||||
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -3,40 +3,43 @@ function f(x) {
|
||||
}
|
||||
|
||||
function testCatch1() {
|
||||
var $tmp;
|
||||
var result;
|
||||
try {
|
||||
result = f("testCatch1");
|
||||
$tmp = f("testCatch1");
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
result = $tmp;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function testCatch2() {
|
||||
var $tmp;
|
||||
var result;
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
result = $tmp;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function testFinally() {
|
||||
var result;
|
||||
try {
|
||||
result = f("testFinally");
|
||||
} finally {
|
||||
}
|
||||
result = f("testFinally");
|
||||
return result;
|
||||
}
|
||||
|
||||
function testOuter() {
|
||||
var $tmp;
|
||||
var result;
|
||||
try {
|
||||
result = f("testOuter");
|
||||
$tmp = f("testOuter");
|
||||
} finally {
|
||||
f("23")
|
||||
}
|
||||
result = $tmp;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -54,4 +57,4 @@ function box() {
|
||||
if (result !== "testOuter") return "testOuter: " + result;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user