From 551ed28d84c588048c0a6f32e78f2f77e79c180f Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 5 May 2016 10:52:10 +0300 Subject: [PATCH] JS/Inlining: introduce removal of unused variables --- .../clean/TemporaryVariableElimination.kt | 58 ++++++++++++++++++- .../TemporaryVariableEliminationTest.kt | 6 ++ .../innerExpressionProcessed.optimized.js | 5 ++ .../innerExpressionProcessed.original.js | 7 +++ .../removeUnused.optimized.js | 30 ++++++++++ .../removeUnused.original.js | 30 ++++++++++ 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.optimized.js create mode 100644 js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.original.js create mode 100644 js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.optimized.js create mode 100644 js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.original.js diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt index 3697c69783b..4a2668b1778 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/TemporaryVariableElimination.kt @@ -33,6 +33,7 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { private val namesToProcess = mutableSetOf() private val statementsToRemove = mutableSetOf() private val namesToSubstitute = mutableSetOf() + private val variablesToRemove = mutableSetOf() fun apply(): Boolean { analyze() @@ -268,7 +269,13 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { } return } + else if (shouldConsiderUnused(name)) { + variablesToRemove += name + handleTopLevel(value) + return + } } + handleTopLevel(expression) } @@ -383,6 +390,9 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { } } else { + if (shouldConsiderUnused(name)) { + variablesToRemove += name + } handleTopLevel(initializer) } } @@ -428,8 +438,33 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { private fun cleanUp() { object : JsVisitorWithContextImpl() { - override fun visit(x: JsVars, ctx: JsContext<*>): Boolean { - x.vars.removeAll { it in statementsToRemove } + override fun visit(x: JsVars, ctx: JsContext): Boolean { + x.vars.removeAll(statementsToRemove) + + var lastDroppedIndex = 0 + for ((index, v) in x.vars.toList().withIndex()) { + val name = v.name + if (name in variablesToRemove) { + hasChanges = true + if (index > lastDroppedIndex) { + val droppedVars = JsVars(*x.vars.subList(lastDroppedIndex, index).toTypedArray()) + droppedVars.synthetic = x.synthetic + ctx.addPrevious(droppedVars) + } + val initExpression = v.initExpression + if (initExpression != null) { + ctx.addPrevious(JsExpressionStatement(initExpression).apply { + synthetic = true + accept(this) + }) + } + lastDroppedIndex = index + 1 + } + } + if (lastDroppedIndex > 0) { + x.vars.subList(0, lastDroppedIndex).clear() + } + if (x.vars.isEmpty()) { ctx.removeMe() hasChanges = true @@ -438,12 +473,27 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { return super.visit(x, ctx) } - override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean { + override fun visit(x: JsExpressionStatement, ctx: JsContext): Boolean { if (x in statementsToRemove) { ctx.removeMe() hasChanges = true return false } + + val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression) + if (assignment != null) { + val (name, value) = assignment + if (name in variablesToRemove) { + hasChanges = true + JsExpressionStatement(value).apply { + synthetic = true + accept(this) + ctx.replaceMe(this) + } + return false + } + } + return super.visit(x, ctx) } @@ -481,6 +531,8 @@ internal class TemporaryVariableElimination(private val root: JsStatement) { usages[name] = (usages[name] ?: 0) + 1 } + private fun shouldConsiderUnused(name: JsName) = (definitions[name] ?: 0) == 1 && (usages[name] ?: 0) == 0 && name in temporary + private fun shouldConsiderTemporary(name: JsName): Boolean { if (definitions[name] ?: 0 != 1 || name !in temporary) return false diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/TemporaryVariableEliminationTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/TemporaryVariableEliminationTest.kt index 9058d112efb..00fd61afdaf 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/TemporaryVariableEliminationTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/TemporaryVariableEliminationTest.kt @@ -34,4 +34,10 @@ class TemporaryVariableEliminationTest : BasicOptimizerTest("temporary-variable" @Test fun nonSideEffect() = box() @Test fun propertyAccess() = box() + + @Test fun removeUnused() = box() + + @Test fun innerExpressionProcessed() = box() + + @Test fun transitiveNotConsideredTrivial() = box() } \ No newline at end of file diff --git a/js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.optimized.js b/js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.optimized.js new file mode 100644 index 00000000000..a12c09fb98e --- /dev/null +++ b/js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.optimized.js @@ -0,0 +1,5 @@ +function box() { + var a = 2; + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.original.js b/js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.original.js new file mode 100644 index 00000000000..5ae8b477928 --- /dev/null +++ b/js/js.translator/testData/js-optimizer/temporary-variable/innerExpressionProcessed.original.js @@ -0,0 +1,7 @@ +function box() { + var a = 2; + var $b = a; + var $c = 2 + $b; + + return "OK"; +} \ No newline at end of file diff --git a/js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.optimized.js b/js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.optimized.js new file mode 100644 index 00000000000..8acd6ff4d1e --- /dev/null +++ b/js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.optimized.js @@ -0,0 +1,30 @@ +var global = 0; + +function test1() { + global++; + return global; +} + +function test2() { + global++; + return global; +} + +function test3() { + global++; + var $b = global--; + return $b + $b; +} + +function box() { + var result = test1(); + if (result != 1) return "fail1: " + result; + + result = test2(); + if (result != 2) return "fail2: " + result; + + result = test3(); + if (result != 6) return "fail3: " + result; + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.original.js b/js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.original.js new file mode 100644 index 00000000000..ececb33ec0f --- /dev/null +++ b/js/js.translator/testData/js-optimizer/temporary-variable/removeUnused.original.js @@ -0,0 +1,30 @@ +var global = 0; + +function test1() { + var $tmp = global++; + return global; +} + +function test2() { + var $tmp; + $tmp = global++; + return global; +} + +function test3() { + var $a = global++, $b = global--; + return $b + $b; +} + +function box() { + var result = test1(); + if (result != 1) return "fail1: " + result; + + result = test2(); + if (result != 2) return "fail2: " + result; + + result = test3(); + if (result != 6) return "fail3: " + result; + + return "OK" +} \ No newline at end of file