JS/Inlining: introduce removal of expression statements without side effects

This commit is contained in:
Alexey Andreev
2016-05-05 12:27:33 +03:00
parent 551ed28d84
commit 65876c36eb
14 changed files with 508 additions and 11 deletions
@@ -103,7 +103,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
RemoveUnusedLocalFunctionDeclarationsKt.removeUnusedLocalFunctionDeclarations(function);
processedFunctions.add(function);
new FunctionPostProcessor(function.getBody()).apply();
new FunctionPostProcessor(function).apply();
assert inProcessFunctions.contains(function);
inProcessFunctions.remove(function);
@@ -16,16 +16,17 @@
package org.jetbrains.kotlin.js.inline.clean
import com.google.dart.compiler.backend.js.ast.JsBlock
import com.google.dart.compiler.backend.js.ast.JsFunction
class FunctionPostProcessor(private val root: JsBlock) {
class FunctionPostProcessor(root: JsFunction) {
val optimizations = listOf(
{ TemporaryAssignmentElimination(root).apply() },
{ RedundantLabelRemoval(root).apply() },
{ TemporaryVariableElimination(root).apply() },
{ IfStatementReduction(root).apply() },
{ DeadCodeElimination(root).apply() },
{ RedundantVariableDeclarationElimination(root).apply() }
{ TemporaryAssignmentElimination(root.body).apply() },
{ RedundantLabelRemoval(root.body).apply() },
{ TemporaryVariableElimination(root.body).apply() },
{ IfStatementReduction(root.body).apply() },
{ DeadCodeElimination(root.body).apply() },
{ RedundantVariableDeclarationElimination(root.body).apply() },
{ IneffectiveStatementElimination(root).apply() }
)
// TODO: reduce to A || B, A && B if possible
@@ -0,0 +1,151 @@
/*
* 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 com.google.dart.compiler.backend.js.ast.*
import com.google.dart.compiler.backend.js.ast.metadata.sideEffects
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
import org.jetbrains.kotlin.js.inline.util.collectLocalVariables
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class IneffectiveStatementElimination(private val root: JsFunction) {
private val localVars = mutableSetOf<JsName>()
private var hasChanges = false
fun apply(): Boolean {
analyze()
process()
return hasChanges
}
private fun analyze() {
localVars += root.collectLocalVariables()
}
private fun process() {
object : JsVisitorWithContextImpl() {
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
if (x.synthetic) {
val replacement = replace(x.expression)
if (replacement.size != 1 || replacement[0] != x.expression) {
hasChanges = true
ctx.addPrevious(replacement.map { JsExpressionStatement(it).apply { synthetic = true } }.toList())
ctx.removeMe()
}
}
return super.visit(x, ctx)
}
}.accept(root.body)
}
private fun replace(expression: JsExpression): List<JsExpression> {
return when (expression) {
is JsNameRef -> {
val qualifier = expression.qualifier
if (qualifier == null && expression.name in localVars) {
listOf()
}
else if (!expression.sideEffects) {
if (qualifier != null) replace(qualifier) else listOf()
}
else {
listOf(expression)
}
}
is JsUnaryOperation -> {
when (expression.operator) {
JsUnaryOperator.DEC, JsUnaryOperator.INC, JsUnaryOperator.DELETE -> listOf(expression)
else -> replace(expression.arg)
}
}
is JsBinaryOperation -> {
if (expression.sideEffects) {
when (expression.operator) {
JsBinaryOperator.AND, JsBinaryOperator.OR -> {
val right = replace(expression.arg2)
if (right.isEmpty()) replace(expression.arg1) else listOf(expression)
}
JsBinaryOperator.INOP, JsBinaryOperator.INSTANCEOF -> listOf(expression)
else -> {
if (!expression.operator.isAssignment) {
replace(expression.arg1) + replace(expression.arg2)
}
else {
listOf(expression)
}
}
}
}
else {
listOf(expression)
}
}
is JsInvocation -> {
if (!expression.sideEffects) {
replace(expression.qualifier) + replaceMany(expression.arguments)
}
else {
listOf(expression)
}
}
is JsNew -> {
if (!expression.sideEffects) {
replace(expression.constructorExpression) + replaceMany(expression.arguments)
}
else {
listOf(expression)
}
}
is JsConditional -> {
val thenExpr = replace(expression.thenExpression)
val elseExpr = replace(expression.elseExpression)
when {
thenExpr.isEmpty() && elseExpr.isEmpty() -> replace(expression.testExpression)
thenExpr.isEmpty() -> listOf(JsAstUtils.or(expression.testExpression, expression.elseExpression))
elseExpr.isEmpty() -> listOf(JsAstUtils.and(expression.testExpression, expression.thenExpression))
else -> listOf(expression)
}
}
is JsArrayAccess -> {
if (!expression.sideEffects) {
replace(expression.arrayExpression) + replace(expression.indexExpression)
}
else {
listOf(expression)
}
}
is JsLiteral.JsValueLiteral -> listOf()
is JsArrayLiteral -> replaceMany(expression.expressions)
is JsObjectLiteral -> expression.propertyInitializers.map { replace(it.labelExpr) + replace(it.valueExpr) }.flatten()
is JsFunction -> if (expression.name == null) listOf() else listOf(expression)
else -> listOf(expression)
}
}
private fun replaceMany(expressions: List<JsExpression>) = expressions.map { replace(it) }.flatten()
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* 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.
@@ -125,6 +125,8 @@ fun collectDefinedNames(scope: JsNode): Set<JsName> {
fun JsFunction.collectFreeVariables() = collectUsedNames(body) - collectDefinedNames(body) - parameters.map { it.name }
fun JsFunction.collectLocalVariables() = collectDefinedNames(body) + parameters.map { it.name }
fun collectJsProperties(scope: JsNode): IdentityHashMap<JsName, JsExpression> {
val collector = PropertyCollector()
collector.accept(scope)
@@ -72,7 +72,7 @@ abstract class BasicOptimizerTest(private var basePath: String) {
for (statement in unoptimizedAst) {
object : RecursiveJsVisitor() {
override fun visitFunction(x: JsFunction) {
FunctionPostProcessor(x.body).apply()
FunctionPostProcessor(x).apply()
super.visitFunction(x)
}
}.accept(statement)
@@ -0,0 +1,29 @@
/*
* 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.test.optimizer
import org.junit.Test
class IneffectiveStatementEliminationTest() : BasicOptimizerTest("ineffective-statement-elimination") {
@Test fun binary() = box()
@Test fun unary() = box()
@Test fun conditional() = box()
@Test fun literal() = box()
}
@@ -0,0 +1,67 @@
var global = 0;
function se() {
return ++global;
}
function se2(value) {
++global;
return value;
}
function test1() {
se(); se(); se(); se(); se();
se(); se(); se(); se(); se();
se(); se(); se(); se(); se();
se(); se(); se(); se(); se();
se(); se(); se(); se(); se();
se(); se(); se(); se(); se();
se(); se(); se();
}
function test2() {
try {
se() in se();
return "fail2a: `in` should not be removed"
}
catch (e) {
// Do nothing
}
try {
se() instanceof se();
return "fail2b: `instanceof` should not be removed"
}
catch (e) {
// Do nothing
}
return "OK"
}
function test3() {
var x = true;
var y = false;
se2(true);
x && se2(true);
se2(true) && se2(true);
se2(false);
y || se2(false);
se2(false) || se2(false);
}
function box() {
test1();
if (global != 33) return "fail1: " + global;
var result = test2();
if (result !== "OK") return result;
if (global != 37) return "fail2: " + global;
test3();
if (global != 45) return "fail3: " + global;
return "OK";
}
@@ -0,0 +1,76 @@
var global = 0;
function se() {
return ++global;
}
function se2(value) {
++global;
return value;
}
function test1() {
var $a = (se() - se() + se()) * se();
var $b = se() % se() / se();
var $c = se() >> se();
var $d = se() << se();
var $e = se() >>> se();
var $f = se() | se() & se() ^ se();
var $g1 = se() == se();
var $g2 = se() === se();
var $g3 = se() != se();
var $g4 = se() !== se();
var $h1 = se() > se();
var $h2 = se() >= se();
var $h3 = se() < se();
var $h4 = se() <= se();
}
function test2() {
try {
var $a = se() in se();
return "fail2a: `in` should not be removed"
}
catch (e) {
// Do nothing
}
try {
var $b = se() instanceof se();
return "fail2b: `instanceof` should not be removed"
}
catch (e) {
// Do nothing
}
return "OK"
}
function test3() {
var x = true;
var y = false;
var $a1 = se2(true) && x;
var $a2 = x && se2(true);
var $a3 = se2(true) && se2(true);
var $a4 = x && x;
var $b1 = se2(false) || y;
var $b2 = y || se2(false);
var $b3 = se2(false) || se2(false);
var $b4 = y || y;
}
function box() {
test1();
if (global != 33) return "fail1: " + global;
var result = test2();
if (result !== "OK") return result;
if (global != 37) return "fail2: " + global;
test3();
if (global != 45) return "fail3: " + global;
return "OK";
}
@@ -0,0 +1,22 @@
var global = 0;
function se(value) {
++global;
return value;
}
function test() {
var x = true;
se(x);
x ? se("foo") : se("bar");
x || se("bar");
x && se("foo");
se(x) ? se("foo") : se("bar");
}
function box() {
test();
if (global != 5) return "fail1: " + global;
return "OK";
}
@@ -0,0 +1,23 @@
var global = 0;
function se(value) {
++global;
return value;
}
function test() {
var x = true;
var $a = se(x) ? "foo" : "bar";
var $b = x ? "foo" : "bar";
var $c = x ? se("foo") : se("bar");
var $d = x ? "foo" : se("bar");
var $e = x ? se("foo") : "bar";
var $f = se(x) ? se("foo") : se("bar");
}
function box() {
test();
if (global != 5) return "fail1: " + global;
return "OK";
}
@@ -0,0 +1,23 @@
var global = 0;
function se(value) {
++global;
return value;
}
function test() {
se(2);
se(3);
se('foo');
se('bar');
se('y');
se(3);
se('null');
}
function box() {
test();
if (global != 7) return "fail: " + test();
return "OK";
}
@@ -0,0 +1,21 @@
var global = 0;
function se(value) {
++global;
return value;
}
function test() {
var $a = { x: se(2), y: se(3) };
var $b = [se("foo"), se("bar")];
var $c = "x" + se("y");
var $d = 2 + se(3);
var $e = se("null") + null;
}
function box() {
test();
if (global != 7) return "fail: " + test();
return "OK";
}
@@ -0,0 +1,41 @@
var global = 0;
function se(value) {
++global;
return value;
}
function test1() {
se(23);
se(23);
se(false);
se(-1);
se(23);
se(23);
}
function test2() {
++global;
global++;
--global;
global--;
}
function test3() {
var obj = { x: 2, y: 3};
delete obj.x;
return ("x" in obj) + ":" + ("y" in obj);
}
function box() {
test1();
if (global != 6) return "fail1: " + global;
test2();
if (global != 6) return "fail2: " + global;
var result = test3();
if (result != "false:true") return "fail3: " + result;
return "OK";
}
@@ -0,0 +1,41 @@
var global = 0;
function se(value) {
++global;
return value;
}
function test1() {
var $a = +se(23);
var $b = -se(23);
var $c = !se(false);
var $d = ~se(-1);
var $e = typeof se(23);
var $f = void se(23);
}
function test2() {
var $a = ++global;
var $b = global++;
var $c = --global;
var $d = global--;
}
function test3() {
var obj = { x: 2, y: 3};
var $tmp = delete obj.x;
return ("x" in obj) + ":" + ("y" in obj);
}
function box() {
test1();
if (global != 6) return "fail1: " + global;
test2();
if (global != 6) return "fail2: " + global;
var result = test3();
if (result != "false:true") return "fail3: " + result;
return "OK";
}