JS/Inlining: introduce removal of expression statements without side effects
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
+151
@@ -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)
|
||||
|
||||
+29
@@ -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()
|
||||
}
|
||||
Vendored
+67
@@ -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";
|
||||
}
|
||||
Vendored
+76
@@ -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";
|
||||
}
|
||||
Vendored
+22
@@ -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";
|
||||
}
|
||||
Vendored
+23
@@ -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";
|
||||
}
|
||||
Vendored
+23
@@ -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";
|
||||
}
|
||||
Vendored
+21
@@ -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";
|
||||
}
|
||||
Vendored
+41
@@ -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";
|
||||
}
|
||||
+41
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user