diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt index 1ba3f9843de..2c174b4a503 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/ExpressionDecomposer.kt @@ -224,6 +224,7 @@ internal class ExpressionDecomposer private constructor( } val lazyEval = JsIf(test, thenBlock, elseBlock) + lazyEval.synthetic = true addStatement(lazyEval) ctx.replaceMe(tmp.nameRef) } 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 3682191eeca..fa95993f524 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 @@ -25,6 +25,8 @@ class FunctionPostProcessor(private val root: JsBlock) { hasChanges = hasChanges or TemporaryAssignmentElimination(root).apply() hasChanges = hasChanges or RedundantLabelRemoval(root).apply() hasChanges = hasChanges or TemporaryVariableElimination(root).apply() + hasChanges = hasChanges or IfStatementReduction(root).apply() + // TODO: reduce to A || B, A && B if possible hasChanges = hasChanges or DeadCodeElimination(root).apply() hasChanges = hasChanges or RedundantVariableDeclarationElimination(root).apply() } while (hasChanges) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/IfStatementReduction.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/IfStatementReduction.kt new file mode 100644 index 00000000000..7fc5d234b59 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/clean/IfStatementReduction.kt @@ -0,0 +1,107 @@ +/* + * 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.synthetic +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils + +class IfStatementReduction(private val root: JsStatement) { + private var hasChanges = false + + fun apply(): Boolean { + visitor.accept(root) + + return hasChanges + } + + val visitor = object : JsVisitorWithContextImpl() { + override fun visit(x: JsIf, ctx: JsContext): Boolean { + var thenStatement = x.thenStatement + var elseStatement = x.elseStatement + if (x.synthetic && elseStatement != null) { + thenStatement = extractSingleStatement(thenStatement) + elseStatement = extractSingleStatement(elseStatement) + + if (thenStatement is JsExpressionStatement && elseStatement is JsExpressionStatement) { + val thenAssignment = JsAstUtils.decomposeAssignment(thenStatement.expression) + val elseAssignment = JsAstUtils.decomposeAssignment(elseStatement.expression) + if (thenAssignment != null && elseAssignment != null) { + val (thenTarget, thenValue) = thenAssignment + val (elseTarget, elseValue) = elseAssignment + if (lhsEqual(thenTarget, elseTarget)) { + hasChanges = true + val ternary = JsConditional(x.ifExpression, thenValue, elseValue) + val replacement = JsExpressionStatement(JsAstUtils.assignment(thenTarget, ternary)) + replacement.synthetic = thenStatement.synthetic && elseStatement.synthetic + ctx.replaceMe(replacement) + accept(replacement) + return false + } + } + } + else if (thenStatement is JsVars && elseStatement is JsVars) { + if (thenStatement.vars.size == 1 && elseStatement.vars.size == 1) { + val thenVar = thenStatement.vars[0] + val elseVar = elseStatement.vars[0] + val thenValue = thenVar.initExpression + val elseValue = elseVar.initExpression + if (thenVar.name == elseVar.name && thenValue != null && elseValue != null) { + hasChanges = true + val ternary = JsConditional(x.ifExpression, thenValue, elseValue) + val replacement = JsAstUtils.newVar(thenVar.name, ternary) + replacement.synthetic = thenStatement.synthetic && elseStatement.synthetic + ctx.replaceMe(replacement) + accept(replacement) + return false + } + } + } + else if (thenStatement is JsReturn && elseStatement is JsReturn) { + val thenValue = thenStatement.expression + val elseValue = elseStatement.expression + if (thenValue != null && elseValue != null) { + hasChanges = true + val ternary = JsConditional(x.ifExpression, thenValue, elseValue) + val replacement = JsReturn(ternary) + accept(replacement) + ctx.replaceMe(replacement) + return false + } + } + } + return super.visit(x, ctx) + } + } + + private fun extractSingleStatement(statement: JsStatement): JsStatement { + var result = statement + while (result is JsBlock && result.statements.size == 1) { + result = result.statements[0] + } + + return result + } + + private fun lhsEqual(a: JsExpression?, b: JsExpression?): Boolean = when { + a == null && b == null -> true + a is JsNameRef && b is JsNameRef -> a.name == b.name && lhsEqual(a.qualifier, b.qualifier) + a is JsArrayAccess && b is JsArrayAccess -> lhsEqual(a.arrayExpression, b.arrayExpression) && + lhsEqual(a.indexExpression, b.indexExpression) + else -> false + } +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineSizeReductionTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineSizeReductionTestGenerated.java index df6e6daa1ee..ed620a2ffe8 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineSizeReductionTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineSizeReductionTestGenerated.java @@ -71,6 +71,12 @@ public class InlineSizeReductionTestGenerated extends AbstractInlineSizeReductio doTest(fileName); } + @TestMetadata("ternaryConditional.kt") + public void testTernaryConditional() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/ternaryConditional.kt"); + doTest(fileName); + } + @TestMetadata("this.kt") public void testThis() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/this.kt"); diff --git a/js/js.translator/testData/inlineSizeReduction/cases/ternaryConditional.kt b/js/js.translator/testData/inlineSizeReduction/cases/ternaryConditional.kt new file mode 100644 index 00000000000..0294f8f4eca --- /dev/null +++ b/js/js.translator/testData/inlineSizeReduction/cases/ternaryConditional.kt @@ -0,0 +1,46 @@ +package foo + +// CHECK_VARS_COUNT: function=test1_za3lpa$ count=0 +// CHECK_VARS_COUNT: function=test2_za3lpa$ count=1 +// CHECK_VARS_COUNT: function=test3_za3lpa$ count=0 + +inline fun a(x: Int) = b(x) + +fun b(x: Int) = x + +fun test1(n: Int) = if (n > 0) a(n + 10) else a(n - 10) + +fun test2(n: Int): Int { + var result = if (n > 0) a(n + 10) else a(n - 10) + return result +} + +fun test3(n: Int): Int { + Holder.value = if (n > 0) a(n + 10) else a(n - 10) + return Holder.value +} + +object Holder { + var value = 0 +} + +fun box(): String { + var result: Int + + result = test1(5) + if (result != 15) return "fail1a: $result" + result = test1(-5) + if (result != -15) return "fail1b: $result" + + result = test2(5) + if (result != 15) return "fail2a: $result" + result = test2(-5) + if (result != -15) return "fail2b: $result" + + result = test3(5) + if (result != 15) return "fail3a: $result" + result = test3(-5) + if (result != -15) return "fail3b: $result" + + return "OK" +} \ No newline at end of file