diff --git a/compiler/testData/codegen/box/coroutines/tailOperations/tailInlining.kt b/compiler/testData/codegen/box/coroutines/tailOperations/tailInlining.kt new file mode 100644 index 00000000000..ac9cce596d1 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailOperations/tailInlining.kt @@ -0,0 +1,53 @@ +// IGNORE_BACKEND: NATIVE +// WITH_RUNTIME +// WITH_COROUTINES +import kotlin.coroutines.experimental.* + +fun box(): String { + async { + val a = foo(23) + log(a) + val b = foo(42) + log(b) + } + while (!finished) { + log("--") + proceed() + } + + if (result != "suspend:23;--;23;suspend:42;--;42;--;done;") return "fail: $result" + + return "OK" +} + +var result = "" + +fun log(message: Any) { + result += "$message;" +} + +var proceed: () -> Unit = { } +var finished = false + +suspend fun bar(x: Int): Int = suspendCoroutine { c -> + log("suspend:$x") + proceed = { c.resume(x) } +} + +inline suspend fun foo(x: Int) = bar(x) + +fun async(a: suspend () -> Unit) { + a.startCoroutine(object : Continuation { + override fun resume(value: Unit) { + proceed = { + log("done") + finished = true + } + } + + override fun resumeWithException(e: Throwable) { + } + + override val context = EmptyCoroutineContext + }) +} \ No newline at end of file diff --git a/compiler/testData/codegen/light-analysis/coroutines/tailOperations/tailInlining.txt b/compiler/testData/codegen/light-analysis/coroutines/tailOperations/tailInlining.txt new file mode 100644 index 00000000000..6d51aa80456 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/coroutines/tailOperations/tailInlining.txt @@ -0,0 +1,42 @@ +@kotlin.Metadata +public final class CoroutineUtilKt { + public final static @org.jetbrains.annotations.NotNull method handleExceptionContinuation(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): kotlin.coroutines.experimental.Continuation + public final static @org.jetbrains.annotations.NotNull method handleResultContinuation(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): kotlin.coroutines.experimental.Continuation +} + +@kotlin.Metadata +public class EmptyContinuation { + public final static field Companion: EmptyContinuation.Companion + private final @org.jetbrains.annotations.NotNull field context: kotlin.coroutines.experimental.CoroutineContext + inner class EmptyContinuation/Companion + public @synthetic.kotlin.jvm.GeneratedByJvmOverloads method (): void + public method (@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.CoroutineContext): void + public synthetic method (p0: kotlin.coroutines.experimental.CoroutineContext, p1: int, p2: kotlin.jvm.internal.DefaultConstructorMarker): void + public @org.jetbrains.annotations.NotNull method getContext(): kotlin.coroutines.experimental.CoroutineContext + public method resume(@org.jetbrains.annotations.Nullable p0: java.lang.Object): void + public method resumeWithException(@org.jetbrains.annotations.NotNull p0: java.lang.Throwable): void +} + +@kotlin.Metadata +public final static class EmptyContinuation/Companion { + inner class EmptyContinuation/Companion + private method (): void +} + +@kotlin.Metadata +public final class TailInliningKt { + private static field finished: boolean + private static @org.jetbrains.annotations.NotNull field proceed: kotlin.jvm.functions.Function0 + private static @org.jetbrains.annotations.NotNull field result: java.lang.String + public final static method async(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void + public final static @org.jetbrains.annotations.Nullable method bar(p0: int, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static @org.jetbrains.annotations.Nullable method foo(p0: int, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final static method getFinished(): boolean + public final static @org.jetbrains.annotations.NotNull method getProceed(): kotlin.jvm.functions.Function0 + public final static @org.jetbrains.annotations.NotNull method getResult(): java.lang.String + public final static method log(@org.jetbrains.annotations.NotNull p0: java.lang.Object): void + public final static method setFinished(p0: boolean): void + public final static method setProceed(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function0): void + public final static method setResult(@org.jetbrains.annotations.NotNull p0: java.lang.String): void +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index f39c23c5a5d..17e0658c70e 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -5589,6 +5589,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailOperations/suspendWithWhen.kt"); doTest(fileName); } + + @TestMetadata("tailInlining.kt") + public void testTailInlining() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailOperations/tailInlining.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/unitTypeReturn") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index c598ec8d622..30e5cf701ff 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5589,6 +5589,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailOperations/suspendWithWhen.kt"); doTest(fileName); } + + @TestMetadata("tailInlining.kt") + public void testTailInlining() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailOperations/tailInlining.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/unitTypeReturn") diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt index 2fc43e0f9c2..d37179da43e 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/metadata/metadataProperties.kt @@ -77,6 +77,8 @@ var HasMetadata.sideEffects: SideEffectKind by MetadataProperty(default = SideEf */ var JsExpression.isSuspend: Boolean by MetadataProperty(default = false) +var JsExpression.isTailCallSuspend: Boolean by MetadataProperty(default = false) + /** * Denotes a reference to coroutine's `result` field that contains result of * last suspended invocation. @@ -100,7 +102,6 @@ var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default class CoroutineMetadata( val doResumeName: JsName, - val resumeName: JsName, val stateName: JsName, val exceptionStateName: JsName, val finallyPathName: JsName, diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt index 2d4b6e39847..64e1064cef3 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/rewriters/ReturnReplacingVisitor.kt @@ -43,9 +43,13 @@ class ReturnReplacingVisitor( ctx.removeMe() + val returnExpression = x.expression val returnReplacement = getReturnReplacement(x.expression) if (returnReplacement != null) { - ctx.addNext(JsExpressionStatement(returnReplacement).apply { synthetic = true }) + if (returnExpression != null && returnExpression.isTailCallSuspend) { + returnReplacement.isSuspend = true + } + ctx.addNext(JsExpressionStatement(returnReplacement)) } if (breakLabel != null) { @@ -68,6 +72,7 @@ class ReturnReplacingVisitor( fun processCoroutineResult(expression: JsExpression?): JsExpression? { if (!isSuspend) return expression + if (expression != null && expression.isTailCallSuspend) return expression val lhs = JsNameRef("\$\$coroutineResult\$\$", JsAstUtils.stateMachineReceiver()).apply { coroutineResult = true } return JsAstUtils.assignment(lhs, expression ?: Namer.getUndefinedExpression()) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index ca7574e46fe..42a74223669 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -6298,6 +6298,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailOperations/suspendWithWhen.kt"); doTest(fileName); } + + @TestMetadata("tailInlining.kt") + public void testTailInlining() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailOperations/tailInlining.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/unitTypeReturn") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt index 6dd45fcba4a..efc651e7bab 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/callTranslator/CallTranslator.kt @@ -22,10 +22,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.js.backend.ast.JsExpression import org.jetbrains.kotlin.js.backend.ast.JsNameRef -import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind -import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineResult -import org.jetbrains.kotlin.js.backend.ast.metadata.isSuspend -import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects +import org.jetbrains.kotlin.js.backend.ast.metadata.* import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.general.Translation import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator @@ -137,13 +134,18 @@ private fun translateFunctionCall( inlineResolvedCall.resultingDescriptor, context) } - if (resolvedCall.resultingDescriptor.isSuspend && context.isInStateMachine) { - context.currentBlock.statements += JsAstUtils.asSyntheticStatement(callExpression.apply { isSuspend = true }) - val coroutineRef = TranslationUtils.translateContinuationArgument(context, resolvedCall) - return context.defineTemporary(JsNameRef("\$\$coroutineResult\$\$", coroutineRef).apply { - sideEffects = SideEffectKind.DEPENDS_ON_STATE - coroutineResult = true - }) + if (resolvedCall.resultingDescriptor.isSuspend) { + if (context.isInStateMachine) { + context.currentBlock.statements += JsAstUtils.asSyntheticStatement(callExpression.apply { isSuspend = true }) + val coroutineRef = TranslationUtils.translateContinuationArgument(context, resolvedCall) + return context.defineTemporary(JsNameRef("\$\$coroutineResult\$\$", coroutineRef).apply { + sideEffects = SideEffectKind.DEPENDS_ON_STATE + coroutineResult = true + }) + } + else { + callExpression.isTailCallSuspend = true + } } return callExpression } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt index d27c66e7bf7..b6f35e5cd5f 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/utils.kt @@ -159,7 +159,6 @@ fun JsFunction.fillCoroutineMetadata( coroutineMetadata = CoroutineMetadata( doResumeName = context.getNameForDescriptor(TranslationUtils.getCoroutineDoResumeFunction(context)), - resumeName = context.getNameForDescriptor(TranslationUtils.getCoroutineResumeFunction(context)), suspendObjectRef = ReferenceTranslator.translateAsValueReference(suspendPropertyDescriptor, context), baseClassRef = coroutineBaseClassRef, stateName = getCoroutinePropertyName("state"),