From cc7f0e2d83fd6367d8f97598bc5b8641276dfe86 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Fri, 3 Feb 2017 16:42:19 +0300 Subject: [PATCH] Fix codegen problem with safe-call on suspension point with two receivers #KT-16145 Fixed --- .../kotlin/codegen/ExpressionCodegen.java | 30 +++++++++-- .../jetbrains/kotlin/codegen/StackValue.java | 8 +++ .../safeCallOnTwoReceivers.kt | 48 +++++++++++++++++ .../safeCallOnTwoReceiversLong.kt | 51 +++++++++++++++++++ .../safeCallOnTwoReceivers.txt | 40 +++++++++++++++ .../safeCallOnTwoReceiversLong.txt | 40 +++++++++++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 12 +++++ .../codegen/BlackBoxCodegenTestGenerated.java | 12 +++++ .../semantics/JsCodegenBoxTestGenerated.java | 12 +++++ 9 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceivers.kt create mode 100644 compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceiversLong.kt create mode 100644 compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceivers.txt create mode 100644 compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceiversLong.txt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 27a1ea6e9f6..e549a1ac218 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -2943,13 +2943,35 @@ public class ExpressionCodegen extends KtVisitor impleme // The solution is to spill stack just after receiver is loaded (after IFNULL) in case of safe call. // But the problem is that we should leave the receiver itself on the stack, so we store it in a temporary variable. if (isSuspensionPoint && isSafeCallOrOnStack) { - int tmpVar = myFrameMap.enterTemp(receiver.type); + boolean bothReceivers = + receiver instanceof StackValue.CallReceiver + && ((StackValue.CallReceiver) receiver).getDispatchReceiver() != null + && ((StackValue.CallReceiver) receiver).getExtensionReceiver() != null; + Type firstReceiverType = + bothReceivers + ? ((StackValue.CallReceiver) receiver).getDispatchReceiver().type + : receiver.type; + + Type secondReceiverType = bothReceivers ? receiver.type : null; + + int tmpVarForFirstReceiver = myFrameMap.enterTemp(firstReceiverType); + int tmpVarForSecondReceiver = -1; + + if (secondReceiverType != null) { + tmpVarForSecondReceiver = myFrameMap.enterTemp(secondReceiverType); + v.store(tmpVarForSecondReceiver, secondReceiverType); + } + v.store(tmpVarForFirstReceiver, firstReceiverType); - v.store(tmpVar, receiver.type); addInlineMarker(v, true); - v.load(tmpVar, receiver.type); - myFrameMap.leaveTemp(receiver.type); + v.load(tmpVarForFirstReceiver, firstReceiverType); + if (secondReceiverType != null) { + v.load(tmpVarForSecondReceiver, secondReceiverType); + myFrameMap.leaveTemp(secondReceiverType); + } + + myFrameMap.leaveTemp(firstReceiverType); } callableMethod.afterReceiverGeneration(v); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java index 3f7582adecf..503771c1236 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java @@ -1596,6 +1596,14 @@ public abstract class StackValue { public void dup(@NotNull InstructionAdapter v, boolean withReceiver) { AsmUtil.dup(v, extensionReceiver.type, dispatchReceiver.type); } + + public StackValue getDispatchReceiver() { + return dispatchReceiver; + } + + public StackValue getExtensionReceiver() { + return extensionReceiver; + } } public abstract static class StackValueWithSimpleReceiver extends StackValue { diff --git a/compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceivers.kt b/compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceivers.kt new file mode 100644 index 00000000000..dd2da5f3fb7 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceivers.kt @@ -0,0 +1,48 @@ +// WITH_RUNTIME +// WITH_COROUTINES +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* +import kotlin.test.assertEquals + +class A(val w: String) { + suspend fun String.ext(): String = suspendCoroutineOrReturn { + x -> + x.resume(this + w) + COROUTINE_SUSPENDED + } +} + +suspend fun A.coroutinebug(v: String?): String { + val r = v?.ext() + if (r == null) return "null" + return r +} + +suspend fun A.coroutinebug2(v: String?): String { + val r = v?.ext() ?: "null" + return r +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var result = "fail 2" + + builder { + val a = A("K") + val x1 = a.coroutinebug(null) + if (x1 != "null") throw RuntimeException("fail 1: $x1") + + val x2 = a.coroutinebug(null) + if (x2 != "null") throw RuntimeException("fail 2: $x2") + + val x3 = a.coroutinebug2(null) + if (x3 != "null") throw RuntimeException("fail 3: $x3") + + result = a.coroutinebug2("O") + } + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceiversLong.kt b/compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceiversLong.kt new file mode 100644 index 00000000000..1be7838af41 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceiversLong.kt @@ -0,0 +1,51 @@ +// WITH_RUNTIME +// WITH_COROUTINES +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* +import kotlin.test.assertEquals + +class A(val w: String) { + suspend fun Long.ext(): String = suspendCoroutineOrReturn { + x -> + x.resume(this.toString() + w) + COROUTINE_SUSPENDED + } +} + +suspend fun A.coroutinebug(v: Long?): String { + val r = v?.ext() + if (r == null) return "null" + return r +} + +suspend fun A.coroutinebug2(v: Long?): String { + val r = v?.ext() ?: "null" + return r +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var result = "fail 2" + + builder { + val a = A("#test") + val x1 = a.coroutinebug(null) + if (x1 != "null") throw RuntimeException("fail 1: $x1") + + val x2 = a.coroutinebug(123456789012345) + if (x2 != "123456789012345#test") throw RuntimeException("fail 2: $x2") + + val x3 = a.coroutinebug2(null) + if (x3 != "null") throw RuntimeException("fail 3: $x3") + + val x4 = a.coroutinebug2(123456789012345) + if (x4 != "123456789012345#test") throw RuntimeException("fail 4: $x4") + + result = "OK" + } + + return result +} diff --git a/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceivers.txt b/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceivers.txt new file mode 100644 index 00000000000..6a01d96089f --- /dev/null +++ b/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceivers.txt @@ -0,0 +1,40 @@ +@kotlin.Metadata +public final class A { + private final @org.jetbrains.annotations.NotNull field w: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.Nullable method ext(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final @org.jetbrains.annotations.NotNull method getW(): java.lang.String +} + +@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 SafeCallOnTwoReceiversKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void + public final static @org.jetbrains.annotations.Nullable method coroutinebug(@org.jetbrains.annotations.NotNull p0: A, @org.jetbrains.annotations.Nullable p1: java.lang.String, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method coroutinebug2(@org.jetbrains.annotations.NotNull p0: A, @org.jetbrains.annotations.Nullable p1: java.lang.String, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.experimental.Continuation): java.lang.Object +} diff --git a/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceiversLong.txt b/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceiversLong.txt new file mode 100644 index 00000000000..4b319ddd094 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/safeCallOnTwoReceiversLong.txt @@ -0,0 +1,40 @@ +@kotlin.Metadata +public final class A { + private final @org.jetbrains.annotations.NotNull field w: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.Nullable method ext(p0: long, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final @org.jetbrains.annotations.NotNull method getW(): java.lang.String +} + +@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 SafeCallOnTwoReceiversLongKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void + public final static @org.jetbrains.annotations.Nullable method coroutinebug(@org.jetbrains.annotations.NotNull p0: A, @org.jetbrains.annotations.Nullable p1: java.lang.Long, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method coroutinebug2(@org.jetbrains.annotations.NotNull p0: A, @org.jetbrains.annotations.Nullable p1: java.lang.Long, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.experimental.Continuation): java.lang.Object +} 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 ca87c6fe82f..ee54da9d8ab 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 @@ -5074,6 +5074,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("safeCallOnTwoReceivers.kt") + public void testSafeCallOnTwoReceivers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceivers.kt"); + doTest(fileName); + } + + @TestMetadata("safeCallOnTwoReceiversLong.kt") + public void testSafeCallOnTwoReceiversLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceiversLong.kt"); + doTest(fileName); + } + @TestMetadata("suspendDestructuringInLambdas.kt") public void testSuspendDestructuringInLambdas() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 29796c370a5..7f60a76c2eb 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5074,6 +5074,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("safeCallOnTwoReceivers.kt") + public void testSafeCallOnTwoReceivers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceivers.kt"); + doTest(fileName); + } + + @TestMetadata("safeCallOnTwoReceiversLong.kt") + public void testSafeCallOnTwoReceiversLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceiversLong.kt"); + doTest(fileName); + } + @TestMetadata("suspendDestructuringInLambdas.kt") public void testSuspendDestructuringInLambdas() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt"); 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 ed429420e09..86bace69f07 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 @@ -5765,6 +5765,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); } + @TestMetadata("safeCallOnTwoReceivers.kt") + public void testSafeCallOnTwoReceivers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceivers.kt"); + doTest(fileName); + } + + @TestMetadata("safeCallOnTwoReceiversLong.kt") + public void testSafeCallOnTwoReceiversLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/safeCallOnTwoReceiversLong.kt"); + doTest(fileName); + } + @TestMetadata("suspendDestructuringInLambdas.kt") public void testSuspendDestructuringInLambdas() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/suspendDestructuringInLambdas.kt");