From 57e75915ff7ca34897fb3902a2c203d76538240b Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Tue, 26 Jun 2018 17:43:09 +0300 Subject: [PATCH] Fixed bug in SuspendFunctionsLowering + test Supported callable references to non-local suspend functions. --- .../kotlin/backend/konan/KonanLower.kt | 2 +- .../konan/lower/SuspendFunctionsLowering.kt | 106 ++++++++++-------- backend.native/tests/build.gradle | 5 + .../codegen/coroutines/anonymousObject.kt | 46 ++++++++ 4 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 backend.native/tests/codegen/coroutines/anonymousObject.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 8c744f1f05c..9ef4a9c143b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -146,7 +146,7 @@ internal class KonanLower(val context: Context) { CompileTimeEvaluateLowering(context).lower(irFile) } phaser.phase(KonanPhase.LOWER_COROUTINES) { - SuspendFunctionsLowering(context).runOnFilePostfix(irFile) + SuspendFunctionsLowering(context).lower(irFile) } phaser.phase(KonanPhase.LOWER_TYPE_OPERATORS) { TypeOperatorLowering(context).runOnFilePostfix(irFile) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SuspendFunctionsLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SuspendFunctionsLowering.kt index 95d5f05f861..c4f924da309 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SuspendFunctionsLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SuspendFunctionsLowering.kt @@ -52,7 +52,7 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.storage.LockBasedStorageManager -internal class SuspendFunctionsLowering(val context: Context): DeclarationContainerLoweringPass { +internal class SuspendFunctionsLowering(val context: Context): FileLoweringPass { private object STATEMENT_ORIGIN_COROUTINE_IMPL : IrStatementOriginImpl("COROUTINE_IMPL") private object DECLARATION_ORIGIN_COROUTINE_IMPL : IrDeclarationOriginImpl("COROUTINE_IMPL") @@ -60,60 +60,72 @@ internal class SuspendFunctionsLowering(val context: Context): DeclarationContai private val builtCoroutines = mutableMapOf() private val suspendLambdas = mutableMapOf() - override fun lower(irDeclarationContainer: IrDeclarationContainer) { - markSuspendLambdas(irDeclarationContainer) - irDeclarationContainer.declarations.transformFlat { - if (it is IrFunction && it.descriptor.isSuspend && it.descriptor.modality != Modality.ABSTRACT) - transformSuspendFunction(it, suspendLambdas[it.descriptor]) + override fun lower(irFile: IrFile) { + markSuspendLambdas(irFile) + buildCoroutines(irFile) + transformCallableReferencesToSuspendLambdas(irFile) + } + + private fun buildCoroutines(irFile: IrFile) { + irFile.declarations.transformFlat(::tryTransformSuspendFunction) + irFile.acceptVoid(object: IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitClass(declaration: IrClass) { + declaration.acceptChildrenVoid(this) + declaration.declarations.transformFlat(::tryTransformSuspendFunction) + } + }) + } + + private fun tryTransformSuspendFunction(element: IrElement) = + if (element is IrFunction && element.descriptor.isSuspend && element.descriptor.modality != Modality.ABSTRACT) + transformSuspendFunction(element, suspendLambdas[element.descriptor]) else null - } - transformCallableReferencesToSuspendLambdas(irDeclarationContainer) + + private fun markSuspendLambdas(irElement: IrElement) { + irElement.acceptChildrenVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitFunctionReference(expression: IrFunctionReference) { + expression.acceptChildrenVoid(this) + + val descriptor = expression.descriptor + if (descriptor.isSuspend) + suspendLambdas.put(descriptor, expression) + } + }) } - private fun markSuspendLambdas(irDeclarationContainer: IrDeclarationContainer) { - irDeclarationContainer.declarations.forEach { - it.acceptChildrenVoid(object: IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } + private fun transformCallableReferencesToSuspendLambdas(irElement: IrElement) { + irElement.transformChildrenVoid(object : IrElementTransformerVoid() { - override fun visitFunctionReference(expression: IrFunctionReference) { - expression.acceptChildrenVoid(this) + override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { + expression.transformChildrenVoid(this) - val descriptor = expression.descriptor - if (descriptor.isSuspend) - suspendLambdas.put(descriptor, expression) - } - }) - } - } - - private fun transformCallableReferencesToSuspendLambdas(irDeclarationContainer: IrDeclarationContainer) { - irDeclarationContainer.declarations.forEach { - it.transformChildrenVoid(object: IrElementTransformerVoid() { - - override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { - expression.transformChildrenVoid(this) - - val descriptor = expression.descriptor - if (!descriptor.isSuspend) - return expression - val coroutine = builtCoroutines[descriptor] - ?: throw Error("Non-local callable reference to suspend lambda: $descriptor") - val constructorParameters = coroutine.coroutineConstructor.valueParameters - val expressionArguments = expression.getArguments().map { it.second } - assert (constructorParameters.size == expressionArguments.size, - { "Inconsistency between callable reference to suspend lambda and the corresponding coroutine" }) - val irBuilder = context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset) - irBuilder.run { - return irCall(coroutine.coroutineConstructor.symbol).apply { - expressionArguments.forEachIndexed { index, argument -> - putValueArgument(index, argument) } + val descriptor = expression.descriptor + if (!descriptor.isSuspend) + return expression + val coroutine = builtCoroutines[descriptor] + ?: throw Error("The coroutine for $descriptor has not been built") + val constructorParameters = coroutine.coroutineConstructor.valueParameters + val expressionArguments = expression.getArguments().map { it.second } + assert(constructorParameters.size == expressionArguments.size, + { "Inconsistency between callable reference to suspend lambda and the corresponding coroutine" }) + val irBuilder = context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset) + irBuilder.run { + return irCall(coroutine.coroutineConstructor.symbol).apply { + expressionArguments.forEachIndexed { index, argument -> + putValueArgument(index, argument) } } } - }) - } + } + }) } private sealed class SuspendFunctionKind { diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index d574497c612..1c21187deb0 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1324,6 +1324,11 @@ task coroutines_coroutineContext2(type: RunKonanTest) { source = "codegen/coroutines/coroutineContext2.kt" } +task coroutines_anonymousObject(type: RunKonanTest) { + goldValue = "zzz\n" + source = "codegen/coroutines/anonymousObject.kt" +} + task AbstractMutableCollection(type: RunKonanTest) { expectedExitStatus = 0 source = "runtime/collections/AbstractMutableCollection.kt" diff --git a/backend.native/tests/codegen/coroutines/anonymousObject.kt b/backend.native/tests/codegen/coroutines/anonymousObject.kt new file mode 100644 index 00000000000..32eed804d4c --- /dev/null +++ b/backend.native/tests/codegen/coroutines/anonymousObject.kt @@ -0,0 +1,46 @@ +package codegen.coroutines.anonymousObject + +import kotlin.test.* + +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation { + companion object : EmptyContinuation() + override fun resume(value: Any?) {} + override fun resumeWithException(exception: Throwable) { throw exception } +} + +suspend fun suspendHere(): Int = suspendCoroutineOrReturn { x -> + x.resume(42) + COROUTINE_SUSPENDED +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +interface I { + suspend fun foo(lambda: suspend (String) -> Unit) + suspend fun bar(s: String) +} + +fun create() = object: I { + var lambda: suspend (String) -> Unit = {} + + override suspend fun foo(lambda: suspend (String) -> Unit) { + this.lambda = lambda + } + + override suspend fun bar(s: String) { + lambda(s) + } +} + +@Test fun runTest() { + builder { + val z = create() + z.foo { suspendHere(); println(it) } + z.bar("zzz") + } +} \ No newline at end of file