diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt index 50d49ee67b2..35e31641507 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionInlineMutator.kt @@ -17,11 +17,15 @@ package org.jetbrains.kotlin.js.inline import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.util.AstUtil import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.js.inline.clean.removeDefaultInitializers import org.jetbrains.kotlin.js.inline.context.InliningContext import org.jetbrains.kotlin.js.inline.context.NamingContext import org.jetbrains.kotlin.js.inline.util.* +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.newVar +import org.jetbrains.kotlin.js.translate.utils.ast.* + import kotlin.platform.platformStatic class FunctionInlineMutator private (private val call: JsInvocation, private val inliningContext: InliningContext) { @@ -31,6 +35,7 @@ class FunctionInlineMutator private (private val call: JsInvocation, private val private val body: JsBlock private var resultExpr: JsExpression? = null private var breakLabel: JsLabel? = null + private val currentStatement = inliningContext.statementContext.getCurrentNode() init { @@ -108,12 +113,6 @@ class FunctionInlineMutator private (private val call: JsInvocation, private val val returnOnTop = ContainerUtil.findInstance(body.getStatements(), javaClass()) val hasReturnOnTopLevel = returnOnTop != null - if (isResultNeeded) { - val resultName = namingContext.getFreshName(getResultLabel()) - namingContext.newVar(resultName, null) - resultExpr = resultName.makeRef() - } - val needBreakLabel = !(returnCount == 1 && hasReturnOnTopLevel) var breakLabelRef: JsNameRef? = null @@ -123,10 +122,65 @@ class FunctionInlineMutator private (private val call: JsInvocation, private val breakLabel = JsLabel(breakName) } + val resultReference = getResultReference() + if (resultReference != null) { + resultExpr = resultReference + } + assert(resultExpr == null || resultExpr is JsNameRef) replaceReturns(body, resultExpr as? JsNameRef, breakLabelRef) } + val breakName = namingContext.getFreshName(getBreakLabel()) + breakLabel = JsLabel(breakName) + + val visitor = ReturnReplacingVisitor(resultExpr as? JsNameRef, breakName.makeRef()) + visitor.accept(body) + } + + private fun getResultReference(): JsNameRef? { + if (!isResultNeeded) return null + + val existingReference = when (currentStatement) { + is JsExpressionStatement -> { + val expression = currentStatement.getExpression() as? JsBinaryOperation + expression?.getResultReference() + } + is JsVars -> currentStatement.getResultReference() + else -> null + } + + if (existingReference != null) return existingReference + + val resultName = namingContext.getFreshName(getResultLabel()) + namingContext.newVar(resultName, null) + return resultName.makeRef() + } + + private fun JsBinaryOperation.getResultReference(): JsNameRef? { + if (operator !== JsBinaryOperator.ASG || arg2 !== call) return null + + return arg1 as? JsNameRef + } + + private fun JsVars.getResultReference(): JsNameRef? { + val vars = getVars() + val variable = vars.first() + + // var a = expr1 + call() is ok, but we don't want to reuse 'a' for result, + // as it means to replace every 'return expr2' to 'a = expr1 + expr2'. + // If there is more than one return, expr1 copies are undesirable. + if (variable.initExpression !== call || vars.size() > 1) return null + + val varName = variable.getName() + with (inliningContext.statementContext) { + removeMe() + addPrevious(newVar(varName, null)) + } + + return varName.makeRef() + } + private fun getArguments(): List { val arguments = call.getArguments() if (isCallInvocation(call)) { @@ -137,8 +191,6 @@ class FunctionInlineMutator private (private val call: JsInvocation, private val } private fun isResultNeeded(call: JsInvocation): Boolean { - val statementContext = inliningContext.statementContext - val currentStatement = statementContext.getCurrentNode() return currentStatement !is JsExpressionStatement || call != currentStatement.getExpression() } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineNoAdditionalVarsCreatedTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineSizeReductionTestGenerated.java similarity index 63% rename from js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineNoAdditionalVarsCreatedTestGenerated.java rename to js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineSizeReductionTestGenerated.java index 12ec285b073..46b0581c948 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineNoAdditionalVarsCreatedTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/InlineSizeReductionTestGenerated.java @@ -27,17 +27,29 @@ import java.util.regex.Pattern; /** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") -@TestMetadata("js/js.translator/testData/inlineNoAdditionalVarsCreated/cases") +@TestMetadata("js/js.translator/testData/inlineSizeReduction/cases") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) -public class InlineNoAdditionalVarsCreatedTestGenerated extends AbstractInlineNoAdditionalVarsCreatedTest { +public class InlineSizeReductionTestGenerated extends AbstractInlineSizeReductionTest { public void testAllFilesPresentInCases() throws Exception { - JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/inlineNoAdditionalVarsCreated/cases"), Pattern.compile("^(.+)\\.kt$"), true); + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/inlineSizeReduction/cases"), Pattern.compile("^(.+)\\.kt$"), true); } @TestMetadata("simpleReturnFunction.kt") public void testSimpleReturnFunction() throws Exception { - String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/inlineNoAdditionalVarsCreated/cases/simpleReturnFunction.kt"); + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/simpleReturnFunction.kt"); + doTest(fileName); + } + + @TestMetadata("valAssignment.kt") + public void testValAssignment() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/valAssignment.kt"); + doTest(fileName); + } + + @TestMetadata("valDeclaration.kt") + public void testValDeclaration() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/valDeclaration.kt"); doTest(fileName); } } diff --git a/js/js.translator/testData/inlineSizeReduction/cases/valAssignment.kt b/js/js.translator/testData/inlineSizeReduction/cases/valAssignment.kt new file mode 100644 index 00000000000..a98af3bceaa --- /dev/null +++ b/js/js.translator/testData/inlineSizeReduction/cases/valAssignment.kt @@ -0,0 +1,22 @@ +package foo + +// CHECK_CONTAINS_NO_CALLS: test +// CHECK_VARS_COUNT: function=test count=1 + +inline fun sum(x: Int, y: Int): Int { + if (x == 0 || y == 0) return 0 + + return x + y +} + +fun test(x: Int, y: Int): Int { + val sum: Int + sum = sum(x, y) + return sum +} + +fun box(): String { + assertEquals(3, test(1, 2)) + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/inlineSizeReduction/cases/valDeclaration.kt b/js/js.translator/testData/inlineSizeReduction/cases/valDeclaration.kt new file mode 100644 index 00000000000..f50198fc39c --- /dev/null +++ b/js/js.translator/testData/inlineSizeReduction/cases/valDeclaration.kt @@ -0,0 +1,21 @@ +package foo + +// CHECK_CONTAINS_NO_CALLS: test +// CHECK_VARS_COUNT: function=test count=1 + +inline fun sum(x: Int, y: Int): Int { + if (x == 0 || y == 0) return 0 + + return x + y +} + +fun test(x: Int, y: Int): Int { + val sum = sum(x, y) + return sum +} + +fun box(): String { + assertEquals(3, test(1, 2)) + + return "OK" +} \ No newline at end of file