diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.java index 71f09e24d91..81824f12754 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.java @@ -67,6 +67,14 @@ public class AnonymousObjectTransformer extends ObjectTransformer deferringMethods = new ArrayList(); + generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, parentRemapper, additionalFakeParams); + for (MethodNode next : methodsToTransform) { MethodVisitor deferringVisitor = newMethod(classBuilder, next); InlineResult funResult = @@ -159,8 +169,6 @@ public class AnonymousObjectTransformer extends ObjectTransformer capturedLambdas = new LinkedHashSet(); //captured var of inlined parameter diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InliningContext.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InliningContext.java index 6103b062b7c..d6edb98e76d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InliningContext.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InliningContext.java @@ -35,6 +35,10 @@ public class InliningContext { public final ReifiedTypeInliner reifiedTypeInliner; public final boolean isInliningLambda; public final boolean classRegeneration; + public final Map internalNameToAnonymousObjectTransformationInfo = + new HashMap(); + + private boolean isContinuation; public InliningContext( @Nullable InliningContext parent, @@ -117,4 +121,21 @@ public class InliningContext { assert parent != null : "At least root context should return proper value"; return parent.getCallSiteInfo(); } + + @Nullable + public AnonymousObjectTransformationInfo findAnonymousObjectTransformationInfo(@NotNull String internalName) { + if (getRoot().internalNameToAnonymousObjectTransformationInfo.containsKey(internalName)) { + return getRoot().internalNameToAnonymousObjectTransformationInfo.get(internalName); + } + + return null; + } + + public boolean isContinuation() { + return isContinuation; + } + + public void setContinuation(boolean continuation) { + isContinuation = continuation; + } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java index ea874d7e480..5b7f4107505 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java @@ -260,16 +260,27 @@ public class MethodInliner { assert transformationInfo instanceof AnonymousObjectTransformationInfo : " call doesn't correspond to object transformation info: " + owner + "." + name + ", info " + transformationInfo; - if (transformationInfo.shouldRegenerate(isSameModule)) { + InliningContext parent = inliningContext.getParent(); + boolean shouldRegenerate = transformationInfo.shouldRegenerate(isSameModule); + if (shouldRegenerate || (parent != null && parent.isContinuation())) { + assert shouldRegenerate || inlineCallSiteInfo.getOwnerClassName().equals(transformationInfo.getOldClassName()) + : "Only coroutines can call their own constructors"; + //put additional captured parameters on stack AnonymousObjectTransformationInfo info = (AnonymousObjectTransformationInfo) transformationInfo; + + AnonymousObjectTransformationInfo oldInfo = inliningContext.findAnonymousObjectTransformationInfo(owner); + if (oldInfo != null) { + info = oldInfo; + } + for (CapturedParamDesc capturedParamDesc : info.getAllRecapturedParameters()) { visitFieldInsn( Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(), "$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor() ); } - super.visitMethodInsn(opcode, transformationInfo.getNewClassName(), name, info.getNewConstructorDescriptor(), itf); + super.visitMethodInsn(opcode, info.getNewClassName(), name, info.getNewConstructorDescriptor(), itf); //TODO: add new inner class also for other contexts if (inliningContext.getParent() instanceof RegeneratedClassContext) { @@ -565,7 +576,9 @@ public class MethodInliner { @NotNull Map lambdaMapping, boolean needReification ) { - return new AnonymousObjectTransformationInfo( + boolean memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType) == null; + + AnonymousObjectTransformationInfo info = new AnonymousObjectTransformationInfo( anonymousType, needReification, lambdaMapping, inliningContext.classRegeneration, isAlreadyRegenerated(anonymousType), @@ -573,6 +586,11 @@ public class MethodInliner { false, inliningContext.nameGenerator ); + + if (memoizeAnonymousObject) { + inliningContext.getRoot().internalNameToAnonymousObjectTransformationInfo.put(anonymousType, info); + } + return info; } private boolean isAlreadyRegenerated(@NotNull String owner) { diff --git a/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda1.kt b/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda1.kt new file mode 100644 index 00000000000..75a2a71e1d1 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda1.kt @@ -0,0 +1,88 @@ +class Controller { + var lastSuspension: Continuation? = null + var result = "fail" + suspend fun suspendHere(x: Continuation) { + lastSuspension = x + } + + fun hasNext() = lastSuspension != null + fun next() { + val x = lastSuspension!! + lastSuspension = null + x.resume("56") + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + val controller1 = Controller() + val controller2 = Controller() + + c(controller1).resume(Unit) + c(controller2).resume(Unit) + + runControllers(controller1, controller2) +} + +fun builder2(coroutine c: Controller.(Long, String) -> Continuation) { + val controller1 = Controller() + val controller2 = Controller() + + c(controller1, 1234567890123456789L, "Q").resume(Unit) + c(controller2, 1234567890123456789L, "Q").resume(Unit) + + runControllers(controller1, controller2) +} + + +private fun runControllers(controller1: Controller, controller2: Controller) { + while (controller1.hasNext()) { + if (!controller2.hasNext()) throw RuntimeException("fail 1") + + if (controller1.lastSuspension === controller2.lastSuspension) throw RuntimeException("equal references") + + controller1.next() + controller2.next() + } + + if (controller2.hasNext()) throw RuntimeException("fail 2") + + if (controller1.result != "OK") throw RuntimeException("fail 3") + if (controller2.result != "OK") throw RuntimeException("fail 4") +} + +inline fun run(b: () -> Unit) { + b() +} + +fun box(): String { + // with capture and params + + var x = "O" + var y = "K" + + // inlined + run { + // no suspension + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + result = x + y + } + + // 1 suspension + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + if (suspendHere() != "56") return@builder2 + result = x + y + } + + // 2 suspensions + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + if (suspendHere() != "56") return@builder2 + suspendHere() + result = x + y + } + } + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda2.kt b/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda2.kt new file mode 100644 index 00000000000..d2e93661f23 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda2.kt @@ -0,0 +1,109 @@ +class Controller { + var lastSuspension: Continuation? = null + var result = "fail" + suspend fun suspendHere(x: Continuation) { + lastSuspension = x + } + + fun hasNext() = lastSuspension != null + fun next() { + val x = lastSuspension!! + lastSuspension = null + x.resume("56") + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + val controller1 = Controller() + val controller2 = Controller() + + c(controller1).resume(Unit) + c(controller2).resume(Unit) + + runControllers(controller1, controller2) +} + +fun builder2(coroutine c: Controller.(Long, String) -> Continuation) { + val controller1 = Controller() + val controller2 = Controller() + + c(controller1, 1234567890123456789L, "Q").resume(Unit) + c(controller2, 1234567890123456789L, "Q").resume(Unit) + + runControllers(controller1, controller2) +} + + +private fun runControllers(controller1: Controller, controller2: Controller) { + while (controller1.hasNext()) { + if (!controller2.hasNext()) throw RuntimeException("fail 1") + + if (controller1.lastSuspension === controller2.lastSuspension) throw RuntimeException("equal references") + + controller1.next() + controller2.next() + } + + if (controller2.hasNext()) throw RuntimeException("fail 2") + + if (controller1.result != "OK") throw RuntimeException("fail 3") + if (controller2.result != "OK") throw RuntimeException("fail 4") +} + +inline fun run(b: () -> Unit) { + b() +} + +fun box(): String { + // with capture and params + var x = "O" + + // inlined + run { + var y = "K" + + { + // no suspension + builder { + result = "OK" + } + + // 1 suspension + builder { + if (suspendHere() != "56") return@builder + result = "OK" + } + + // 2 suspensions + builder { + if (suspendHere() != "56") return@builder + suspendHere() + result = "OK" + } + + // no suspension + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + result = x + y + } + + // 1 suspension + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + if (suspendHere() != "56") return@builder2 + result = x + y + } + + // 2 suspensions + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + if (suspendHere() != "56") return@builder2 + suspendHere() + result = x + y + } + + }() + } + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda3.kt b/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda3.kt new file mode 100644 index 00000000000..a54bf7ae7af --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda3.kt @@ -0,0 +1,107 @@ +class Controller { + var lastSuspension: Continuation? = null + var result = "fail" + suspend fun suspendHere(x: Continuation) { + lastSuspension = x + } + + fun hasNext() = lastSuspension != null + fun next() { + val x = lastSuspension!! + lastSuspension = null + x.resume("56") + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + val controller1 = Controller() + val controller2 = Controller() + + c(controller1).resume(Unit) + c(controller2).resume(Unit) + + runControllers(controller1, controller2) +} + +fun builder2(coroutine c: Controller.(Long, String) -> Continuation) { + val controller1 = Controller() + val controller2 = Controller() + + c(controller1, 1234567890123456789L, "Q").resume(Unit) + c(controller2, 1234567890123456789L, "Q").resume(Unit) + + runControllers(controller1, controller2) +} + + +private fun runControllers(controller1: Controller, controller2: Controller) { + while (controller1.hasNext()) { + if (!controller2.hasNext()) throw RuntimeException("fail 1") + + if (controller1.lastSuspension === controller2.lastSuspension) throw RuntimeException("equal references") + + controller1.next() + controller2.next() + } + + if (controller2.hasNext()) throw RuntimeException("fail 2") + + if (controller1.result != "OK") throw RuntimeException("fail 3") + if (controller2.result != "OK") throw RuntimeException("fail 4") +} + +inline fun run(b: () -> Unit) { + b() +} + +fun box(): String { + var x = "O" + + { + var y = "K" + // inlined + run { + // no suspension + builder { + result = "OK" + } + + // 1 suspension + builder { + if (suspendHere() != "56") return@builder + result = "OK" + } + + // 2 suspensions + builder { + if (suspendHere() != "56") return@builder + suspendHere() + result = "OK" + } + + + // no suspension + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + result = x + y + } + + // 1 suspension + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + if (suspendHere() != "56") return@builder2 + result = x + y + } + + // 2 suspensions + builder2 { a, b -> + if (a != 1234567890123456789L || b != "Q" ) return@builder2 + if (suspendHere() != "56") return@builder2 + suspendHere() + result = x + y + } + } + } () + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index b2b807b83fe..d4c0ebe046c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4231,6 +4231,24 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("multipleInvokeCallsInsideInlineLambda1.kt") + public void testMultipleInvokeCallsInsideInlineLambda1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda1.kt"); + doTest(fileName); + } + + @TestMetadata("multipleInvokeCallsInsideInlineLambda2.kt") + public void testMultipleInvokeCallsInsideInlineLambda2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda2.kt"); + doTest(fileName); + } + + @TestMetadata("multipleInvokeCallsInsideInlineLambda3.kt") + public void testMultipleInvokeCallsInsideInlineLambda3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda3.kt"); + doTest(fileName); + } + @TestMetadata("nestedTryCatch.kt") public void testNestedTryCatch() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/nestedTryCatch.kt");