From 060f08a8dd95d252fbfa0e4fc55da948802e9dd4 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 5 Dec 2016 15:30:04 +0300 Subject: [PATCH] JS: add support of interceptResume function in coroutine controller --- .../common/commonCoroutineCodegenUtil.kt | 6 +++ .../codegen/coroutines/CoroutineCodegen.kt | 1 + .../coroutines/coroutineCodegenUtil.kt | 10 +--- .../codegen/box/coroutines/interceptResume.kt | 53 +++++++++++++++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 6 +++ .../codegen/BlackBoxCodegenTestGenerated.java | 6 +++ .../js/ast/metadata/metadataProperties.kt | 2 + .../coroutine/CoroutineFunctionTransformer.kt | 26 +++++++-- .../kotlin/js/coroutine/CoroutinePasses.kt | 18 +++++-- .../semantics/JsCodegenBoxTestGenerated.java | 6 +++ .../expression/LiteralFunctionTranslator.kt | 16 ++++++ 11 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 compiler/testData/codegen/box/coroutines/interceptResume.kt diff --git a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt index df3f91bcce7..2ec90ef7072 100644 --- a/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt +++ b/compiler/backend-common/src/org/jetbrains/kotlin/backend/common/commonCoroutineCodegenUtil.kt @@ -18,9 +18,12 @@ package org.jetbrains.kotlin.backend.common import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.util.OperatorNameConventions val SUSPEND_WITH_CURRENT_CONTINUATION_NAME = Name.identifier("suspendWithCurrentContinuation") @@ -30,4 +33,7 @@ fun FunctionDescriptor.getBuiltInSuspendWithCurrentContinuation() = ?.getContributedFunctions(SUSPEND_WITH_CURRENT_CONTINUATION_NAME, NoLookupLocation.FROM_BACKEND) ?.singleOrNull() +fun KotlinType.findInterceptResume() = findOperatorInController(this, OperatorNameConventions.COROUTINE_INTERCEPT_RESUME) +fun findOperatorInController(controllerType: KotlinType, name: Name): SimpleFunctionDescriptor? = + controllerType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).singleOrNull { it.isOperator } \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt index a48c42fd83a..e11c68dfb45 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.codegen.coroutines +import org.jetbrains.kotlin.backend.common.findOperatorInController import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.context.ClosureContext diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt index e4c3e6f0ba3..986626f87fb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt @@ -29,7 +29,6 @@ import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.descriptors.SourceElement import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl -import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.* @@ -240,14 +239,9 @@ private fun FunctionDescriptor.getContinuationParameterTypeOfSuspendFunction() = val KotlinBuiltIns.continuationClassDescriptor get() = getBuiltInClassByFqName(DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME) -fun KotlinType.hasInlineInterceptResume() = - findOperatorInController(this, OperatorNameConventions.COROUTINE_INTERCEPT_RESUME)?.isInline == true +fun KotlinType.hasInlineInterceptResume() = findInterceptResume()?.isInline == true -fun KotlinType.hasNoinlineInterceptResume() = - findOperatorInController(this, OperatorNameConventions.COROUTINE_INTERCEPT_RESUME)?.isInline == false - -fun findOperatorInController(controllerType: KotlinType, name: Name): SimpleFunctionDescriptor? = - controllerType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).singleOrNull { it.isOperator } +fun KotlinType.hasNoinlineInterceptResume() = findInterceptResume()?.isInline == false fun FunctionDescriptor.isBuiltInSuspendWithCurrentContinuation(): Boolean { if (name != SUSPEND_WITH_CURRENT_CONTINUATION_NAME) return false diff --git a/compiler/testData/codegen/box/coroutines/interceptResume.kt b/compiler/testData/codegen/box/coroutines/interceptResume.kt new file mode 100644 index 00000000000..9b46d4be343 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/interceptResume.kt @@ -0,0 +1,53 @@ +// NO_INTERCEPT_RESUME_TESTS + +class Controller { + var log = "" + var resumeIndex = 0 + + suspend fun suspendWithValue(value: T): T = suspendWithCurrentContinuation { continuation -> + log += "suspend($value);" + continuation.resume(value) + Suspend + } + + suspend fun suspendWithException(value: String): Unit = suspendWithCurrentContinuation { continuation -> + log += "error($value);" + continuation.resumeWithException(RuntimeException(value)) + Suspend + } + + operator fun interceptResume(block: () -> Unit) { + var id = resumeIndex++ + log += "before $id;" + block() + log += "after $id;" + } +} + +fun test(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.log +} + +fun box(): String { + var result = test { + val o = suspendWithValue("O") + val k = suspendWithValue("K") + log += "$o$k;" + } + if (result != "before 0;suspend(O);before 1;suspend(K);before 2;OK;after 2;after 1;after 0;") return "fail1: $result" + + result = test { + try { + suspendWithException("OK") + log += "ignore;" + } + catch (e: RuntimeException) { + log += "${e.message};" + } + } + if (result != "before 0;error(OK);before 1;OK;after 1;after 0;") return "fail2: $result" + + return "OK" +} \ No newline at end of file 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 592d446d70e..a617bdcaa9c 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 @@ -4595,6 +4595,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("interceptResume.kt") + public void testInterceptResume() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/interceptResume.kt"); + doTest(fileName); + } + @TestMetadata("iterateOverArray.kt") public void testIterateOverArray() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/iterateOverArray.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index a7f9ff72325..0eff7853017 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4595,6 +4595,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("interceptResume.kt") + public void testInterceptResume() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/interceptResume.kt"); + doTest(fileName); + } + @TestMetadata("iterateOverArray.kt") public void testIterateOverArray() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/iterateOverArray.kt"); diff --git a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt index dcca6e7ce10..12d2587d57f 100644 --- a/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt +++ b/js/js.dart-ast/src/com/google/dart/compiler/backend/js/ast/metadata/metadataProperties.kt @@ -109,6 +109,8 @@ var JsFunction.continuationInterfaceRef: JsExpression? by MetadataProperty(defau var JsName.imported by MetadataProperty(default = false) +var JsFunction.interceptResumeRef: JsExpression? by MetadataProperty(default = null) + enum class TypeCheck { TYPEOF, INSTANCEOF, diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt index 6342e1ef9cb..68a70547b85 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt @@ -61,7 +61,7 @@ class CoroutineFunctionTransformer( val additionalStatements = mutableListOf() val resumeName = generateDoResume(coroutineBlocks, context, additionalStatements, throwName) generateContinuationConstructor(context, additionalStatements, bodyTransformer.hasFinallyBlocks, globalCatchBlockIndex) - generateContinuationMethods(resumeName, additionalStatements) + generateContinuationMethods(context, resumeName, additionalStatements) generateCoroutineInstantiation() @@ -116,12 +116,18 @@ class CoroutineFunctionTransformer( return JsAstUtils.assignment(JsNameRef(Namer.METADATA, constructorName.makeRef()), metadataObject).makeStmt() } - private fun generateContinuationMethods(doResumeName: JsName, statements: MutableList) { - generateResumeFunction(doResumeName, statements, "resume", "data", listOf()) - generateResumeFunction(doResumeName, statements, "resumeWithException", "exception", listOf(Namer.getUndefinedExpression())) + private fun generateContinuationMethods( + context: CoroutineTransformationContext, + doResumeName: JsName, + statements: MutableList + ) { + generateResumeFunction(context, doResumeName, statements, "resume", "data", listOf()) + generateResumeFunction(context, doResumeName, statements, "resumeWithException", "exception", + listOf(Namer.getUndefinedExpression())) } private fun generateResumeFunction( + context: CoroutineTransformationContext, doResumeName: JsName, statements: MutableList, name: String, @@ -137,9 +143,19 @@ class CoroutineFunctionTransformer( resumeFunction.parameters += JsParameter(resumeParameter) resumeFunction.body.statements.apply { + val interceptResumeRef = function.interceptResumeRef val invocation = JsInvocation(JsNameRef(doResumeName, JsLiteral.THIS), additionalArgs + resumeParameter.makeRef()) - this += JsReturn(invocation) + if (interceptResumeRef == null) { + this += JsReturn(invocation) + } + else { + val interceptLambda = JsFunction(resumeFunction.scope, JsBlock(JsReturn(invocation)), "") + val interceptParameter = JsInvocation(JsNameRef("bind", interceptLambda), JsLiteral.THIS) + val interceptInvocation = JsInvocation(interceptResumeRef.deepCopy(), interceptParameter) + this += JsReturn(interceptInvocation) + } } + resumeFunction.body.replaceSpecialReferences(context) statements.apply { assignToPrototype(resumeName, resumeFunction) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt index 60905514f49..4bc6c2fe379 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt @@ -198,7 +198,7 @@ private fun CoroutineBlock.collectFinallyPaths(): List> { return finallyPaths } -fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set) { +fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) { val visitor = object : JsVisitorWithContextImpl() { override fun endVisit(x: JsNameRef, ctx: JsContext) { when { @@ -213,11 +213,19 @@ fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformati sideEffects = SideEffectKind.DEPENDS_ON_STATE }) } + } + } + } + visitor.accept(this) +} - x.qualifier == null && x.name in localVariables -> { - val fieldName = scope.getFieldName(x.name!!) - ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS)) - } +fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set) { + replaceSpecialReferences(context) + val visitor = object : JsVisitorWithContextImpl() { + override fun endVisit(x: JsNameRef, ctx: JsContext) { + if (x.qualifier == null && x.name in localVariables) { + val fieldName = scope.getFieldName(x.name!!) + ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS)) } } 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 11d025c5639..07cbbbda277 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 @@ -5406,6 +5406,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("interceptResume.kt") + public void testInterceptResume() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/interceptResume.kt"); + doTest(fileName); + } + @TestMetadata("iterateOverArray.kt") public void testIterateOverArray() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/iterateOverArray.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt index b74ace34019..ef585503fb1 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/LiteralFunctionTranslator.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.expression import com.google.dart.compiler.backend.js.ast.* import com.google.dart.compiler.backend.js.ast.metadata.* +import org.jetbrains.kotlin.backend.common.findInterceptResume import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl @@ -96,6 +97,8 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato null } + val interceptResumeRef = controllerType?.let { context().generateInterceptResumeRef(it) } + if (tracker.hasCapturedExceptContaining()) { val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda) lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor) @@ -108,6 +111,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato lambdaCreator.name.staticRef = lambdaCreator lambdaCreator.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference() lambdaCreator.suspendObjectRef = suspendObjectRef + lambdaCreator.interceptResumeRef = interceptResumeRef return lambdaCreator.withCapturedParameters(descriptor, descriptor.wrapContextForCoroutineIfNecessary(functionContext), invokingContext) } @@ -120,6 +124,7 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato lambda.name.staticRef = lambda lambda.continuationInterfaceRef = invokingContext.getContinuationInterfaceReference() lambda.suspendObjectRef = suspendObjectRef + lambda.interceptResumeRef = interceptResumeRef return getReferenceToLambda(invokingContext, descriptor, lambda.name) } @@ -201,6 +206,17 @@ private fun getReferenceToLambda(context: TranslationContext, descriptor: Callab } } +private fun TranslationContext.generateInterceptResumeRef(classDescriptor: ClassDescriptor): JsExpression? { + val interceptResumeFunction = classDescriptor.defaultType.findInterceptResume() + return if (interceptResumeFunction != null) { + val controllerRef = JsNameRef("\$\$controller\$\$", JsLiteral.THIS).apply { coroutineController = true } + JsNameRef(getNameForDescriptor(interceptResumeFunction), controllerRef) + } + else { + null + } +} + private data class CapturedArgsParams(val arguments: List = listOf(), val parameters: List = listOf()) /**