From 71b15910440be1f9fc1bc9924516feaa987c61f3 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 3 Nov 2017 14:35:39 +0300 Subject: [PATCH] JS: replace suspend inline metadata after inlining This fixes some issues on coroutine inlining, see tests --- .../js/ast/DebugJsAstProtoBuf.java | 14 +++- .../indirectInlineUsedAsNonInline.kt | 50 +++++++++++++ .../inlineMultiModuleWithInnerInlining.kt | 75 +++++++++++++++++++ .../coroutines/multiModule/inlineTailCall.kt | 29 +++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 18 +++++ .../codegen/BlackBoxCodegenTestGenerated.java | 18 +++++ .../LightAnalysisModeTestGenerated.java | 18 +++++ .../ast/metadata/metadataProperties.kt | 5 +- .../js/coroutine/CoroutineTransformer.kt | 10 +++ .../kotlin/js/inline/ExpressionDecomposer.kt | 4 +- .../kotlin/js/inline/FunctionReader.kt | 37 +++++++-- .../util/rewriters/ReturnReplacingVisitor.kt | 7 +- js/js.libraries/src/js/markerFunctions.js | 3 + js/js.serializer/src/js-ast.proto | 1 + .../serialization/js/ast/JsAstDeserializer.kt | 1 + .../serialization/js/ast/JsAstProtoBuf.java | 9 +++ .../serialization/js/ast/JsAstSerializer.kt | 1 + .../semantics/JsCodegenBoxTestGenerated.java | 18 +++++ .../declaration/AbstractDeclarationVisitor.kt | 4 +- .../declaration/inlineCoroutineUtil.kt | 60 +++++++++------ .../kotlin/js/translate/utils/JsAstUtils.java | 22 +----- 21 files changed, 344 insertions(+), 60 deletions(-) create mode 100644 compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt create mode 100644 compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt create mode 100644 compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt diff --git a/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java b/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java index d89f07ec27f..f1358a9e924 100644 --- a/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java +++ b/build-common/test/org/jetbrains/kotlin/serialization/js/ast/DebugJsAstProtoBuf.java @@ -227,6 +227,10 @@ public final class DebugJsAstProtoBuf { * COROUTINE_RECEIVER = 8; */ COROUTINE_RECEIVER(7, 8), + /** + * SET_COROUTINE_RESULT = 9; + */ + SET_COROUTINE_RESULT(8, 9), ; /** @@ -261,6 +265,10 @@ public final class DebugJsAstProtoBuf { * COROUTINE_RECEIVER = 8; */ public static final int COROUTINE_RECEIVER_VALUE = 8; + /** + * SET_COROUTINE_RESULT = 9; + */ + public static final int SET_COROUTINE_RESULT_VALUE = 9; public final int getNumber() { return value; } @@ -275,6 +283,7 @@ public final class DebugJsAstProtoBuf { case 6: return COROUTINE_RESULT; case 7: return COROUTINE_CONTROLLER; case 8: return COROUTINE_RECEIVER; + case 9: return SET_COROUTINE_RESULT; default: return null; } } @@ -49553,12 +49562,13 @@ public final class DebugJsAstProtoBuf { "\013SideEffects\022\021\n\rAFFECTS_STATE\020\001\022\024\n\020DEPEN" + "DS_ON_STATE\020\002\022\010\n\004PURE\020\003*?\n\016InlineStrateg" + "y\022\017\n\013AS_FUNCTION\020\000\022\014\n\010IN_PLACE\020\001\022\016\n\nNOT_" + - "INLINE\020\002*\275\001\n\017SpecialFunction\022\032\n\026DEFINE_I" + + "INLINE\020\002*\327\001\n\017SpecialFunction\022\032\n\026DEFINE_I" + "NLINE_FUNCTION\020\001\022\021\n\rWRAP_FUNCTION\020\002\022\021\n\rT" + "O_BOXED_CHAR\020\003\022\016\n\nUNBOX_CHAR\020\004\022\020\n\014SUSPEN" + "D_CALL\020\005\022\024\n\020COROUTINE_RESULT\020\006\022\030\n\024COROUT" + "INE_CONTROLLER\020\007\022\026\n\022COROUTINE_RECEIVER\020\010", - "B\024B\022DebugJsAstProtoBuf" + "\022\030\n\024SET_COROUTINE_RESULT\020\tB\024B\022DebugJsAst" + + "ProtoBuf" }; org.jetbrains.kotlin.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new org.jetbrains.kotlin.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { diff --git a/compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt b/compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt new file mode 100644 index 00000000000..07b6ed72e8b --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt @@ -0,0 +1,50 @@ +// WITH_RUNTIME +// WITH_COROUTINES +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +var stopped = false +var log = "" +var postponed: () -> Unit = { } + +suspend fun delay(): Unit = suspendCoroutine { c -> + log += "1" + postponed = { + log += "3" + c.resume(Unit) + } + COROUTINE_SUSPENDED +} + +suspend inline fun foo(x: String): String { + delay() + return x +} + +suspend inline fun bar(x: String) = foo(x) + +suspend fun baz(x: String) = bar(x) + +fun act(c: suspend () -> Unit) { + stopped = false + + c.startCoroutine(handleResultContinuation { + stopped = true + }) + + while (!stopped) { + log += "2" + postponed() + } +} + +fun box(): String { + var result = "" + act { + result = baz("OK") + } + + if (log != "123") return "fail: $log" + return result +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt new file mode 100644 index 00000000000..0b1cfebcd66 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt @@ -0,0 +1,75 @@ +// IGNORE_BACKEND: NATIVE +// WITH_COROUTINES +// WITH_RUNTIME + +// MODULE: lib(support) +// FILE: lib.kt + +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +var continuation: () -> Unit = { } +var log = "" +var finished = false + +suspend fun foo(v: T): T = suspendCoroutineOrReturn { x -> + continuation = { + x.resume(v) + } + log += "foo($v);" + COROUTINE_SUSPENDED +} + +inline suspend fun boo(v: String): String { + foo("!$v") + log += "boo($v);" + return foo(v) +} + +inline suspend fun bar(v: String): String { + val x = boo(v) + log += "bar($x);" + return x +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(handleResultContinuation { + continuation = { } + finished = true + }) +} + +// MODULE: main(lib) +// FILE: main.kt + +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +suspend fun baz() { + val a = bar("A") + log += "$a;" + log += "between bar;" + val b = bar("B") + log += "$b;" +} + +val expectedString = + "foo(!A);@;boo(A);foo(A);@;bar(A);A;" + + "between bar;" + + "foo(!B);@;boo(B);foo(B);@;bar(B);B;" + +fun box(): String { + builder { + baz() + } + + while (!finished) { + log += "@;" + continuation() + } + + if (log != expectedString) return "fail: $log" + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt b/compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt new file mode 100644 index 00000000000..edb8ccb72ae --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt @@ -0,0 +1,29 @@ +// IGNORE_BACKEND: NATIVE +// WITH_RUNTIME +// WITH_COROUTINES +// MODULE: lib +// FILE: lib.kt +suspend inline fun foo(v: String): String = v + +suspend inline fun bar(): String = foo("O") + +// MODULE: main(lib, support) +// FILE: main.kt +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var result = "" + + builder { + result = bar() + result += foo("K") + } + + return result +} 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 1124c391897..17e7815e58d 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 @@ -5513,6 +5513,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("indirectInlineUsedAsNonInline.kt") + public void testIndirectInlineUsedAsNonInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt"); + doTest(fileName); + } + @TestMetadata("inlineFunInGenericClass.kt") public void testInlineFunInGenericClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlineFunInGenericClass.kt"); @@ -6133,6 +6139,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("inlineMultiModuleWithInnerInlining.kt") + public void testInlineMultiModuleWithInnerInlining() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt"); + doTest(fileName); + } + + @TestMetadata("inlineTailCall.kt") + public void testInlineTailCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index e879650d311..1db20d31e53 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5513,6 +5513,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("indirectInlineUsedAsNonInline.kt") + public void testIndirectInlineUsedAsNonInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt"); + doTest(fileName); + } + @TestMetadata("inlineFunInGenericClass.kt") public void testInlineFunInGenericClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlineFunInGenericClass.kt"); @@ -6133,6 +6139,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("inlineMultiModuleWithInnerInlining.kt") + public void testInlineMultiModuleWithInnerInlining() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt"); + doTest(fileName); + } + + @TestMetadata("inlineTailCall.kt") + public void testInlineTailCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index c614b0b3c9c..a7d1e64913a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -5513,6 +5513,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("indirectInlineUsedAsNonInline.kt") + public void testIndirectInlineUsedAsNonInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt"); + doTest(fileName); + } + @TestMetadata("inlineFunInGenericClass.kt") public void testInlineFunInGenericClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlineFunInGenericClass.kt"); @@ -6133,6 +6139,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("inlineMultiModuleWithInnerInlining.kt") + public void testInlineMultiModuleWithInnerInlining() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt"); + doTest(fileName); + } + + @TestMetadata("inlineTailCall.kt") + public void testInlineTailCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); 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 39e5cb6e558..ce547fc1977 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 @@ -115,6 +115,8 @@ var JsNameRef.coroutineReceiver by MetadataProperty(default = false) var JsFunction.forceStateMachine by MetadataProperty(default = false) +var JsFunction.isInlineableCoroutineBody by MetadataProperty(default = false) + var JsName.imported by MetadataProperty(default = false) var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default = null) @@ -156,7 +158,8 @@ enum class SpecialFunction(val suggestedName: String) { SUSPEND_CALL("suspendCall"), COROUTINE_RESULT("coroutineResult"), COROUTINE_CONTROLLER("coroutineController"), - COROUTINE_RECEIVER("coroutineReceiver") + COROUTINE_RECEIVER("coroutineReceiver"), + SET_COROUTINE_RESULT("setCoroutineResult") } enum class BoxingKind { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt index de2050a5f4f..3087ee17d14 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformer.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.js.coroutine import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata +import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody +import org.jetbrains.kotlin.js.translate.declaration.transformCoroutineMetadataToSpecialFunctions import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.utils.JsAstUtils @@ -57,6 +59,14 @@ class CoroutineTransformer : JsVisitorWithContextImpl() { return super.visit(x, ctx) } + override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean { + if (x.isInlineableCoroutineBody) { + x.body = transformCoroutineMetadataToSpecialFunctions(x.body) + return false + } + return super.visit(x, ctx) + } + override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean { val initExpression = x.initExpression if (initExpression != null) { 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 4db8056c901..4b46d3e0979 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 @@ -63,7 +63,9 @@ internal class ExpressionDecomposer private constructor( val decomposer = with (statement) { val extractable = match(canBeExtractedByInliner) val containsExtractable = withParentsOfNodes(extractable) - val nodesWithSideEffect = match { it !is JsLiteral.JsValueLiteral } + val nodesWithSideEffect = match { + !(it is JsLiteral.JsValueLiteral || (it is JsExpression && it.sideEffects == SideEffectKind.PURE)) + } val containsNodeWithSideEffect = withParentsOfNodes(nodesWithSideEffect) ExpressionDecomposer(containsExtractable, containsNodeWithSideEffect) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index 941a55f41f3..23b85483141 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -233,7 +233,8 @@ class FunctionReader( } val position = info.offsetToSourceMapping[offset] - val functionExpr = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram())) ?: + val jsScope = JsRootScope(JsProgram()) + val functionExpr = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, jsScope) ?: return null functionExpr.fixForwardNameReferences() val (function, wrapper) = if (isWrapped) { @@ -261,12 +262,7 @@ class FunctionReader( wrapperStatements?.forEach { replaceExternalNames(it, replacements, allDefinedNames) } function.markInlineArguments(descriptor) markDefaultParams(function) - - for (externalName in (collectReferencedNames(function) - allDefinedNames)) { - info.specialFunctions[externalName.ident]?.let { - externalName.specialFunction = it - } - } + markSpecialFunctions(function, allDefinedNames, info, jsScope) val namesWithoutSizeEffects = wrapperStatements.orEmpty().asSequence() .flatMap { collectDefinedNames(it).asSequence() } @@ -289,6 +285,33 @@ class FunctionReader( return FunctionWithWrapper(function, wrapper) } + private fun markSpecialFunctions(function: JsFunction, allDefinedNames: Set, info: ModuleInfo, scope: JsScope) { + for (externalName in (collectReferencedNames(function) - allDefinedNames)) { + info.specialFunctions[externalName.ident]?.let { + externalName.specialFunction = it + } + } + + function.body.accept(object : RecursiveJsVisitor() { + override fun visitNameRef(nameRef: JsNameRef) { + super.visitNameRef(nameRef) + markQualifiedSpecialFunction(nameRef) + } + + private fun markQualifiedSpecialFunction(nameRef: JsNameRef) { + val qualifier = nameRef.qualifier as? JsNameRef ?: return + if (qualifier.ident != info.kotlinVariable || qualifier.qualifier != null) return + if (nameRef.name?.specialFunction != null) return + + val specialFunction = specialFunctionsByName[nameRef.ident] ?: return + if (nameRef.name == null) { + nameRef.name = scope.declareName(nameRef.ident) + } + nameRef.name!!.specialFunction = specialFunction + } + }) + } + private fun markDefaultParams(function: JsFunction) { val paramsByNames = function.parameters.associate { it.name to it } for (ifStatement in function.body.statements) { 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 b34e7fe59b8..e3e248b90b0 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 @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.inline.util.rewriters import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.* +import org.jetbrains.kotlin.js.coroutine.isStateMachineResult import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.utils.JsAstUtils @@ -43,12 +44,8 @@ class ReturnReplacingVisitor( ctx.removeMe() - val returnExpression = x.expression val returnReplacement = getReturnReplacement(x.expression) if (returnReplacement != null) { - if (returnExpression != null && returnExpression.isTailCallSuspend) { - returnReplacement.isSuspend = true - } if (returnReplacement.source == null) { returnReplacement.source = x.source } @@ -74,7 +71,7 @@ class ReturnReplacingVisitor( } private fun processCoroutineResult(expression: JsExpression?): JsExpression? { - if (!isSuspend) return expression + if (!isSuspend || expression.isStateMachineResult()) return expression val lhs = JsNameRef("\$\$coroutineResult\$\$", JsAstUtils.stateMachineReceiver()).apply { coroutineResult = true } return JsAstUtils.assignment(lhs, expression ?: Namer.getUndefinedExpression()) } diff --git a/js/js.libraries/src/js/markerFunctions.js b/js/js.libraries/src/js/markerFunctions.js index 05aef8b00ce..15c2fc30754 100644 --- a/js/js.libraries/src/js/markerFunctions.js +++ b/js/js.libraries/src/js/markerFunctions.js @@ -75,6 +75,9 @@ Kotlin.coroutineController = function(qualifier) { Kotlin.coroutineReceiver = function(qualifier) { }; +Kotlin.setCoroutineResult = function(value, qualifier) { +}; + Kotlin.getFunctionById = function(id, defaultValue) { return function() { return defaultValue; diff --git a/js/js.serializer/src/js-ast.proto b/js/js.serializer/src/js-ast.proto index eca42fe7395..f1b8e723446 100644 --- a/js/js.serializer/src/js-ast.proto +++ b/js/js.serializer/src/js-ast.proto @@ -440,6 +440,7 @@ enum SpecialFunction { COROUTINE_RESULT = 6; COROUTINE_CONTROLLER = 7; COROUTINE_RECEIVER = 8; + SET_COROUTINE_RESULT = 9; } diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt index 06d5549fa20..b146778dd38 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt @@ -527,6 +527,7 @@ class JsAstDeserializer(program: JsProgram, private val sourceRoots: Iterable SpecialFunction.COROUTINE_RESULT JsAstProtoBuf.SpecialFunction.COROUTINE_CONTROLLER -> SpecialFunction.COROUTINE_CONTROLLER JsAstProtoBuf.SpecialFunction.COROUTINE_RECEIVER -> SpecialFunction.COROUTINE_RECEIVER + JsAstProtoBuf.SpecialFunction.SET_COROUTINE_RESULT -> SpecialFunction.SET_COROUTINE_RESULT } private fun withLocation(fileId: Int?, location: Location?, action: () -> T): T { diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java index ff4995aa0e7..ea82a1beca8 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstProtoBuf.java @@ -175,6 +175,10 @@ public final class JsAstProtoBuf { * COROUTINE_RECEIVER = 8; */ COROUTINE_RECEIVER(7, 8), + /** + * SET_COROUTINE_RESULT = 9; + */ + SET_COROUTINE_RESULT(8, 9), ; /** @@ -209,6 +213,10 @@ public final class JsAstProtoBuf { * COROUTINE_RECEIVER = 8; */ public static final int COROUTINE_RECEIVER_VALUE = 8; + /** + * SET_COROUTINE_RESULT = 9; + */ + public static final int SET_COROUTINE_RESULT_VALUE = 9; public final int getNumber() { return value; } @@ -223,6 +231,7 @@ public final class JsAstProtoBuf { case 6: return COROUTINE_RESULT; case 7: return COROUTINE_CONTROLLER; case 8: return COROUTINE_RECEIVER; + case 9: return SET_COROUTINE_RESULT; default: return null; } } diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt index 08e3a0775cb..efd792785bd 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt @@ -563,6 +563,7 @@ class JsAstSerializer(private val pathResolver: (File) -> String) { SpecialFunction.COROUTINE_RESULT -> JsAstProtoBuf.SpecialFunction.COROUTINE_RESULT SpecialFunction.COROUTINE_CONTROLLER -> JsAstProtoBuf.SpecialFunction.COROUTINE_CONTROLLER SpecialFunction.COROUTINE_RECEIVER -> JsAstProtoBuf.SpecialFunction.COROUTINE_RECEIVER + SpecialFunction.SET_COROUTINE_RESULT -> JsAstProtoBuf.SpecialFunction.SET_COROUTINE_RESULT } private fun serialize(name: JsName): Int = nameMap.getOrPut(name) { 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 371b623fb9c..5958ea4e180 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 @@ -6083,6 +6083,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("indirectInlineUsedAsNonInline.kt") + public void testIndirectInlineUsedAsNonInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt"); + doTest(fileName); + } + @TestMetadata("inlineFunInGenericClass.kt") public void testInlineFunInGenericClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlineFunInGenericClass.kt"); @@ -6685,6 +6691,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("inlineMultiModuleWithInnerInlining.kt") + public void testInlineMultiModuleWithInnerInlining() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt"); + doTest(fileName); + } + + @TestMetadata("inlineTailCall.kt") + public void testInlineTailCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt"); + doTest(fileName); + } + @TestMetadata("simple.kt") public void testSimple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt index 8275f367347..247c8767d53 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/AbstractDeclarationVisitor.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.js.backend.ast.JsExpression import org.jetbrains.kotlin.js.backend.ast.JsFunction import org.jetbrains.kotlin.js.backend.ast.JsName import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata +import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody import org.jetbrains.kotlin.js.descriptorUtils.shouldBeExported import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.expression.* @@ -100,9 +101,10 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor() { if (descriptor.isSuspend && descriptor.isInline && descriptor.shouldBeExported(context.config) && functionAndContext != null) { val innerContext = functionAndContext.second - val inlineFunction = transformCoroutineMetadataToSpecialFunctions(context, functionAndContext.first.deepCopy() as JsFunction) + val inlineFunction = functionAndContext.first.deepCopy() as JsFunction inlineFunction.name = null inlineFunction.coroutineMetadata = null + inlineFunction.isInlineableCoroutineBody = true val metadata = InlineMetadata.compose(inlineFunction, descriptor, innerContext) val functionWithMetadata = metadata.functionWithMetadata(context, descriptor.source.getPsi()) context.addDeclarationStatement(functionWithMetadata.makeStmt()) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt index 7c14f3749ff..cbcc5c6becf 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/inlineCoroutineUtil.kt @@ -14,31 +14,16 @@ * limitations under the License. */ -/* - * Copyright 2010-2017 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.translate.declaration import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.* -import org.jetbrains.kotlin.js.translate.context.TranslationContext -import org.jetbrains.kotlin.js.translate.utils.TranslationUtils +import org.jetbrains.kotlin.js.translate.context.Namer +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.assignment +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn import org.jetbrains.kotlin.js.translate.utils.name -fun transformCoroutineMetadataToSpecialFunctions(context: TranslationContext, node: T): T { +fun transformCoroutineMetadataToSpecialFunctions(node: T): T { val visitor = object : JsVisitorWithContextImpl() { override fun endVisit(x: JsNameRef, ctx: JsContext) { val specialFunction = when { @@ -49,7 +34,11 @@ fun transformCoroutineMetadataToSpecialFunctions(context: Translati } if (specialFunction != null) { val arguments = listOfNotNull(x.qualifier).toTypedArray() - ctx.replaceMe(TranslationUtils.invokeSpecialFunction(context, specialFunction, *arguments).source(x.source)) + ctx.replaceMe(JsInvocation(specialFunction.ref(), *arguments).apply { + synthetic = x.synthetic + sideEffects = x.sideEffects + source = x.source + }) } else { super.endVisit(x, ctx) @@ -59,13 +48,29 @@ fun transformCoroutineMetadataToSpecialFunctions(context: Translati override fun endVisit(x: JsExpression, ctx: JsContext) { if (x.isSuspend) { x.isSuspend = false - ctx.replaceMe(TranslationUtils.invokeSpecialFunction(context, SpecialFunction.SUSPEND_CALL, x).source(x.source)) + ctx.replaceMe(JsInvocation(SpecialFunction.SUSPEND_CALL.ref(), x).source(x.source)) } } + + override fun visit(x: JsBinaryOperation, ctx: JsContext): Boolean { + val lhs = x.arg1 + if (lhs is JsNameRef && lhs.coroutineResult) { + val arguments = listOf(accept(x.arg2)) + listOfNotNull(lhs.qualifier?.let { accept(it) }) + ctx.replaceMe(JsInvocation(SpecialFunction.SET_COROUTINE_RESULT.ref(), arguments).apply { + synthetic = x.synthetic + sideEffects = x.sideEffects + source = x.source + }) + return false + } + return super.visit(x, ctx) + } } return visitor.accept(node) } +private fun SpecialFunction.ref() = pureFqn(suggestedName, Namer.kotlinObject()) + fun transformSpecialFunctionsToCoroutineMetadata(node: T): T { val visitor = object : JsVisitorWithContextImpl() { override fun endVisit(x: JsInvocation, ctx: JsContext) { @@ -91,9 +96,20 @@ fun transformSpecialFunctionsToCoroutineMetadata(node: T): T { isSuspend = true } } + SpecialFunction.SET_COROUTINE_RESULT -> { + val lhs = JsNameRef("\$result\$", x.arguments.getOrNull(1)).apply { + coroutineResult = true + } + assignment(lhs, x.arguments[0]) + } else -> null } - replacement?.let { ctx.replaceMe(it) } + replacement?.let { + it.source = x.source + it.sideEffects = x.sideEffects + it.synthetic = x.synthetic + ctx.replaceMe(it) + } } } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java index a534164a134..336a7d1d0c4 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java @@ -454,26 +454,6 @@ public final class JsAstUtils { return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value))); } - public static JsExpression replaceRootReference(@NotNull JsNameRef fullQualifier, @NotNull JsExpression newQualifier) { - if (fullQualifier.getQualifier() == null) { - assert Namer.getRootPackageName().equals(fullQualifier.getIdent()) : "Expected root package, but: " + fullQualifier.getIdent(); - return newQualifier; - } - - fullQualifier = fullQualifier.deepCopy(); - JsNameRef qualifier = fullQualifier; - while (true) { - JsExpression parent = qualifier.getQualifier(); - assert parent instanceof JsNameRef : "unexpected qualifier: " + parent + ", original: " + fullQualifier; - if (((JsNameRef) parent).getQualifier() == null) { - assert Namer.getRootPackageName().equals(((JsNameRef) parent).getIdent()); - qualifier.setQualifier(newQualifier); - return fullQualifier; - } - qualifier = (JsNameRef) parent; - } - } - @NotNull public static List flattenStatement(@NotNull JsStatement statement) { if (statement instanceof JsBlock) { @@ -527,7 +507,7 @@ public final class JsAstUtils { @NotNull public static JsExpression stateMachineReceiver() { - JsNameRef result = new JsNameRef("$this$"); + JsNameRef result = pureFqn("$this$", null); MetadataProperties.setCoroutineReceiver(result, true); return result; }