diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt index 7a61018ba7b..4ceacd3031b 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/FunctionPostProcessor.kt @@ -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) } -} \ No newline at end of file +} diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt deleted file mode 100644 index 9e641d855d9..00000000000 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryAssignmentElimination.kt +++ /dev/null @@ -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() - - // 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() - private val statementsToRemove = mutableSetOf() - private val usageSequences = mutableMapOf() - private val syntheticNames = mutableSetOf() - private var hasChanges = false - private val namesToProcess = mutableSetOf() - - 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): 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 - } -} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/TemporaryAssignmentEliminationTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/TemporaryAssignmentTest.kt similarity index 90% rename from js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/TemporaryAssignmentEliminationTest.kt rename to js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/TemporaryAssignmentTest.kt index 44b0a831df6..c9048e05ded 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/TemporaryAssignmentEliminationTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/optimizer/TemporaryAssignmentTest.kt @@ -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() -} \ No newline at end of file +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/assignment.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/assignment.optimized.js index b86d64e43d8..3783fc61ebb 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/assignment.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/assignment.optimized.js @@ -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" -} \ No newline at end of file +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/declaration.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/declaration.optimized.js index b86d64e43d8..44b2185e0ff 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/declaration.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/declaration.optimized.js @@ -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" -} \ No newline at end of file +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js index 203a5b85262..9aa8a230856 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/ifWithoutElse.optimized.js @@ -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"; -} \ No newline at end of file +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/returnStatement.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/returnStatement.optimized.js index 02c5403a532..a5382912594 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/returnStatement.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/returnStatement.optimized.js @@ -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" -} \ No newline at end of file +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveAssignment.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveAssignment.optimized.js index 9ad9a4a2c40..5766b6a8b00 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveAssignment.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveAssignment.optimized.js @@ -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" -} \ No newline at end of file +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js index f1c820193fc..351a917141e 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/transitiveChain.optimized.js @@ -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" -} \ No newline at end of file +} diff --git a/js/js.translator/testData/js-optimizer/temporary-assignment/tryCatch.optimized.js b/js/js.translator/testData/js-optimizer/temporary-assignment/tryCatch.optimized.js index 29f9144ebf0..7852e18953e 100644 --- a/js/js.translator/testData/js-optimizer/temporary-assignment/tryCatch.optimized.js +++ b/js/js.translator/testData/js-optimizer/temporary-assignment/tryCatch.optimized.js @@ -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"; -} \ No newline at end of file +}