diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt index 198b69d2567..71264d75cb8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt @@ -41,6 +41,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame import org.jetbrains.org.objectweb.asm.tree.analysis.SourceInterpreter import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue @@ -689,14 +690,16 @@ private fun allSuspensionPointsAreTailCalls( methodNode: MethodNode, suspensionPoints: List ): Boolean { - val safelyReachableReturns = findSafelyReachableReturns(methodNode) val sourceFrames = MethodTransformer.analyze(thisName, methodNode, IgnoringCopyOperationSourceInterpreter()) + val safelyReachableReturns = findSafelyReachableReturns(methodNode, sourceFrames) val instructions = methodNode.instructions return suspensionPoints.all { suspensionPoint -> val beginIndex = instructions.indexOf(suspensionPoint.suspensionCallBegin) val endIndex = instructions.indexOf(suspensionPoint.suspensionCallEnd) + if (isUnreachable(beginIndex, sourceFrames)) return@all true + val insideTryBlock = methodNode.tryCatchBlocks.any { block -> val tryBlockStartIndex = instructions.indexOf(block.start) val tryBlockEndIndex = instructions.indexOf(block.end) @@ -728,7 +731,7 @@ internal class IgnoringCopyOperationSourceInterpreter : SourceInterpreter() { * * @return indices of safely reachable returns for each instruction in the method node */ -private fun findSafelyReachableReturns(methodNode: MethodNode): Array?> { +private fun findSafelyReachableReturns(methodNode: MethodNode, sourceFrames: Array?>): Array?> { val controlFlowGraph = ControlFlowGraph.build(methodNode) val insns = methodNode.instructions @@ -736,6 +739,7 @@ private fun findSafelyReachableReturns(methodNode: MethodNode): Array?> val insn = insns[index] if (insn.opcode == Opcodes.ARETURN) { + if (isUnreachable(index, sourceFrames)) return@init null return@init setOf(index) } @@ -770,6 +774,9 @@ private fun findSafelyReachableReturns(methodNode: MethodNode): Array?> return reachableReturnsIndices } +// Check whether this instruction is unreachable, i.e. there is no path leading to this instruction +internal fun isUnreachable(index: Int, sourceFrames: Array?>) = sourceFrames[index] == null + private fun AbstractInsnNode?.isInvisibleInDebugVarInsn(methodNode: MethodNode): Boolean { val insns = methodNode.instructions val index = insns.indexOf(this) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/ReturnUnitMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/ReturnUnitMethodTransformer.kt index aa8fa278d89..e7468cf52f3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/ReturnUnitMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/ReturnUnitMethodTransformer.kt @@ -112,7 +112,9 @@ object ReturnUnitMethodTransformer : MethodTransformer() { ): Map> { val frames = analyze(internalClassName, methodNode, IgnoringCopyOperationSourceInterpreter()) return pops.keysToMap { - frames[methodNode.instructions.indexOf(it)].getStack(0).insns + val index = methodNode.instructions.indexOf(it) + if (isUnreachable(index, frames)) return@keysToMap emptySet() + frames[index].getStack(0).insns } } diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unreachable.kt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unreachable.kt new file mode 100644 index 00000000000..7cf6550f850 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unreachable.kt @@ -0,0 +1,24 @@ +// WITH_RUNTIME +// WITH_COROUTINES +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +suspend fun twoReturns(c: suspend () -> Unit) { + return c() + throw RuntimeException("FAIL 1") +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var res = "FAIL" + builder { + twoReturns { + res = "OK" + } + } + return res +} diff --git a/compiler/testData/codegen/bytecodeListing/unreachable.kt b/compiler/testData/codegen/bytecodeListing/unreachable.kt new file mode 100644 index 00000000000..cb5d8276f2d --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/unreachable.kt @@ -0,0 +1,118 @@ +suspend fun empty() {} +suspend fun withoutReturn() { + empty() +} + +suspend fun twoReturns() { + return empty() + return empty() +} + +suspend fun notTailCall() { + empty() + return empty() + empty() +} + +suspend fun lambdaAsParameter(c: suspend () -> Unit) { + c() +} + +suspend fun lambdaAsParameterNotTailCall(c: suspend () -> Unit) { + c() + return c() + c() +} + +suspend fun lambdaAsParameterReturn(c: suspend () -> Unit) { + return c() + c() +} + +suspend fun returnsInt() = 42 +suspend fun callsIntNotTailCall() { + returnsInt() + return + empty() +} + +suspend fun multipleExitPoints(b: Boolean) { + if (b) empty() else withoutReturn() + return + empty() +} + +suspend fun multipleExitPointsNotTailCall(b: Boolean) { + if (b) empty() else returnsInt() + return + empty() +} + +fun ordinary() = 1 +inline fun ordinaryInline() { + ordinary() +} + +suspend fun multipleExitPointsWithOrdinaryInline(b: Boolean) { + if (b) empty() else ordinaryInline() + return + empty() +} + +suspend fun multipleExitPointsWhen(i: Int) { + when (i) { + 1 -> empty() + 2 -> twoReturns() + 3 -> withoutReturn() + else -> lambdaAsParameter {} + } + return + empty() +} + +suspend fun generic(): T = TODO() +suspend fun useGenericReturningUnit() { + generic() + return + empty() +} + +class Generic { + suspend fun foo(): T = TODO() +} + +suspend fun useGenericClass(g: Generic) { + g.foo() + return + empty() +} + +suspend fun genericInferType(c: () -> T): T = TODO() +suspend fun useGenericInferType() { + genericInferType {} + return + empty() +} + +suspend fun nullableUnit(): Unit? = null +suspend fun useNullableUnit() { + nullableUnit() + return + empty() +} + +suspend fun useRunRunRunRunRun() { + run { + run { + run { + run { + run { + empty() + } + } + } + } + } + return + empty() +} diff --git a/compiler/testData/codegen/bytecodeListing/unreachable.txt b/compiler/testData/codegen/bytecodeListing/unreachable.txt new file mode 100644 index 00000000000..0a507515788 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/unreachable.txt @@ -0,0 +1,115 @@ +@kotlin.Metadata +public final class Generic { + public method (): void + public final @org.jetbrains.annotations.Nullable method foo(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object +} + +@kotlin.Metadata +final class UnreachableKt$callsIntNotTailCall$1 { + synthetic field data: java.lang.Object + synthetic field exception: java.lang.Throwable + inner class UnreachableKt$callsIntNotTailCall$1 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + synthetic final method getLabel(): int + synthetic final method setLabel(p0: int): void +} + +@kotlin.Metadata +final class UnreachableKt$lambdaAsParameterNotTailCall$1 { + field L$0: java.lang.Object + synthetic field data: java.lang.Object + synthetic field exception: java.lang.Throwable + inner class UnreachableKt$lambdaAsParameterNotTailCall$1 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + synthetic final method getLabel(): int + synthetic final method setLabel(p0: int): void +} + +@kotlin.Metadata +final class UnreachableKt$multipleExitPointsNotTailCall$1 { + field Z$0: boolean + synthetic field data: java.lang.Object + synthetic field exception: java.lang.Throwable + inner class UnreachableKt$multipleExitPointsNotTailCall$1 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + synthetic final method getLabel(): int + synthetic final method setLabel(p0: int): void +} + +@kotlin.Metadata +final class UnreachableKt$multipleExitPointsWhen$2 { + inner class UnreachableKt$multipleExitPointsWhen$2 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method create(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object +} + +@kotlin.Metadata +final class UnreachableKt$notTailCall$1 { + synthetic field data: java.lang.Object + synthetic field exception: java.lang.Throwable + inner class UnreachableKt$notTailCall$1 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + synthetic final method getLabel(): int + synthetic final method setLabel(p0: int): void +} + +@kotlin.Metadata +final class UnreachableKt$useGenericInferType$2 { + public final static field INSTANCE: UnreachableKt$useGenericInferType$2 + inner class UnreachableKt$useGenericInferType$2 + static method (): void + method (): void + public synthetic method invoke(): java.lang.Object + public final method invoke(): void +} + +@kotlin.Metadata +final class UnreachableKt$useNullableUnit$1 { + synthetic field data: java.lang.Object + synthetic field exception: java.lang.Throwable + inner class UnreachableKt$useNullableUnit$1 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + synthetic final method getLabel(): int + synthetic final method setLabel(p0: int): void +} + +@kotlin.Metadata +public final class UnreachableKt { + inner class UnreachableKt$callsIntNotTailCall$1 + inner class UnreachableKt$lambdaAsParameterNotTailCall$1 + inner class UnreachableKt$multipleExitPointsNotTailCall$1 + inner class UnreachableKt$multipleExitPointsWhen$2 + inner class UnreachableKt$notTailCall$1 + inner class UnreachableKt$useGenericInferType$2 + inner class UnreachableKt$useNullableUnit$1 + public final static @org.jetbrains.annotations.Nullable method callsIntNotTailCall(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method empty(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method generic(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method genericInferType(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function0, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method lambdaAsParameter(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method lambdaAsParameterNotTailCall(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method lambdaAsParameterReturn(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method multipleExitPoints(p0: boolean, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method multipleExitPointsNotTailCall(p0: boolean, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method multipleExitPointsWhen(p0: int, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method multipleExitPointsWithOrdinaryInline(p0: boolean, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method notTailCall(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method nullableUnit(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static method ordinary(): int + public final static method ordinaryInline(): void + public final static @org.jetbrains.annotations.Nullable method returnsInt(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method twoReturns(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method useGenericClass(@org.jetbrains.annotations.NotNull p0: Generic, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method useGenericInferType(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method useGenericReturningUnit(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method useNullableUnit(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method useRunRunRunRunRun(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method withoutReturn(@org.jetbrains.annotations.Nullable p0: java.lang.Object): 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 60fd5c3f801..a145fba6bc7 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 @@ -6394,6 +6394,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/tryCatch.kt"); doTest(fileName); } + + @TestMetadata("unreachable.kt") + public void testUnreachable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unreachable.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index eac7ed920a3..7dec6bd70c0 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -6394,6 +6394,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/tryCatch.kt"); doTest(fileName); } + + @TestMetadata("unreachable.kt") + public void testUnreachable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unreachable.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java index 7d21f53917c..77323f04cf8 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java @@ -169,6 +169,12 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest { doTest(fileName); } + @TestMetadata("unreachable.kt") + public void testUnreachable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeListing/unreachable.kt"); + doTest(fileName); + } + @TestMetadata("compiler/testData/codegen/bytecodeListing/annotations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 0f1b06c27e2..9b5c6b12b84 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -6394,6 +6394,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/tryCatch.kt"); doTest(fileName); } + + @TestMetadata("unreachable.kt") + public void testUnreachable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unreachable.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations") 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 3aec3282254..7cc460cb969 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 @@ -6952,6 +6952,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/tryCatch.kt"); doTest(fileName); } + + @TestMetadata("unreachable.kt") + public void testUnreachable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/unreachable.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations")