diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 871377e6386..9801257d07d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -2497,14 +2497,14 @@ public class ExpressionCodegen extends KtVisitor impleme } public int lookupLocalIndex(DeclarationDescriptor descriptor) { - return myFrameMap.getIndex(getParameterSynonymOrThis(descriptor)); - } - - private DeclarationDescriptor getParameterSynonymOrThis(DeclarationDescriptor descriptor) { - if (!(descriptor instanceof ValueParameterDescriptor)) return descriptor; + int index = myFrameMap.getIndex(descriptor); + if (index != -1) return index; + if (!(descriptor instanceof ValueParameterDescriptor)) return -1; DeclarationDescriptor synonym = bindingContext.get(CodegenBinding.PARAMETER_SYNONYM, (ValueParameterDescriptor) descriptor); - return synonym != null ? synonym : descriptor; + if (synonym == null) return -1; + + return myFrameMap.getIndex(synonym); } @NotNull diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/TailRecursionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/TailRecursionCodegen.java index 02a4e7bc616..8538efa9e12 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/TailRecursionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/TailRecursionCodegen.java @@ -20,6 +20,7 @@ import com.google.common.collect.Lists; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.cfg.TailRecursionKind; import org.jetbrains.kotlin.codegen.context.MethodContext; +import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.descriptors.FunctionDescriptor; @@ -65,7 +66,7 @@ public class TailRecursionCodegen { } public void generateTailRecursion(ResolvedCall resolvedCall) { - CallableDescriptor fd = resolvedCall.getResultingDescriptor(); + CallableDescriptor fd = CoroutineCodegenUtilKt.unwrapInitialDescriptorForSuspendFunction(resolvedCall.getResultingDescriptor()); assert fd instanceof FunctionDescriptor : "Resolved call doesn't refer to the function descriptor: " + fd; CallableMethod callable = (CallableMethod) codegen.resolveToCallable((FunctionDescriptor) fd, false, resolvedCall); @@ -73,6 +74,11 @@ public class TailRecursionCodegen { if (arguments == null) { throw new IllegalStateException("Failed to arrange value arguments by index: " + fd); } + + if (((FunctionDescriptor) fd).isSuspend()) { + AsmUtil.pop(v, callable.getValueParameters().get(callable.getValueParameters().size() - 1).getAsmType()); + } + assignParameterValues(fd, callable, arguments); if (callable.getExtensionReceiverType() != null) { if (resolvedCall.getExtensionReceiver() != fd.getExtensionReceiverParameter().getValue()) { 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 3db52687c25..974b6d11b4a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt @@ -27,7 +27,6 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl -import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtDeclarationWithBody @@ -41,11 +40,10 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.KotlinTypeFactory -import org.jetbrains.kotlin.types.typeUtil.asTypeProjection import org.jetbrains.kotlin.types.typeUtil.makeNullable import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.singletonOrEmptyList +import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.MethodVisitor import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type @@ -114,7 +112,7 @@ class CoroutineCodegen private constructor( } override fun generateClosureBody() { - for (parameter in allLambdaParameters()) { + for (parameter in allFunctionParameters()) { val fieldInfo = parameter.getFieldInfoForCoroutineLambdaParameter() v.newField( OtherOrigin(parameter), @@ -153,7 +151,7 @@ class CoroutineCodegen private constructor( } }) - if (allLambdaParameters().size <= 1) { + if (allFunctionParameters().size <= 1) { val delegate = typeMapper.mapSignatureSkipGeneric(createCoroutineDescriptor).asmMethod val bridgeParameters = (1..delegate.argumentTypes.size - 1).map { AsmTypes.OBJECT_TYPE } + delegate.argumentTypes.last() @@ -239,7 +237,7 @@ class CoroutineCodegen private constructor( } // load resultContinuation - load(allLambdaParameters().map { typeMapper.mapType(it.type).size }.sum() + 1, AsmTypes.OBJECT_TYPE) + load(allFunctionParameters().map { typeMapper.mapType(it.type).size }.sum() + 1, AsmTypes.OBJECT_TYPE) invokespecial(owner.internalName, constructorToUseFromInvoke.name, constructorToUseFromInvoke.descriptor, false) @@ -248,7 +246,7 @@ class CoroutineCodegen private constructor( // Pass lambda parameters to 'invoke' call on newly constructed object var index = 1 - for (parameter in allLambdaParameters()) { + for (parameter in allFunctionParameters()) { val fieldInfoForCoroutineLambdaParameter = parameter.getFieldInfoForCoroutineLambdaParameter() load(index, fieldInfoForCoroutineLambdaParameter.fieldType) AsmUtil.genAssignInstanceFieldFromParam(fieldInfoForCoroutineLambdaParameter, index, this, cloneIndex) @@ -261,24 +259,32 @@ class CoroutineCodegen private constructor( } private fun ExpressionCodegen.initializeCoroutineParameters() { - for (parameter in allLambdaParameters()) { - val mappedType = typeMapper.mapType(parameter.type) - val newIndex = myFrameMap.enter(parameter, mappedType) + if (!isSuspendLambda && !originalSuspendFunctionDescriptor.isTailrec) return + for (parameter in allFunctionParameters()) { + val fieldStackValue = + if (isSuspendLambda) + StackValue.field( + parameter.getFieldInfoForCoroutineLambdaParameter(), generateThisOrOuter(context.thisDescriptor, false) + ) + else + closureContext.lookupInContext(parameter, null, state, /* ignoreNoOuter = */ false) - generateLoadField(parameter.getFieldInfoForCoroutineLambdaParameter()) + val mappedType = typeMapper.mapType(parameter.type) + fieldStackValue.put(mappedType, v) + + val newIndex = myFrameMap.enter(parameter, mappedType) v.store(newIndex, mappedType) } + + // necessary for proper tailrec codegen + val actualMethodStartLabel = Label() + v.visitLabel(actualMethodStartLabel) + context.setMethodStartLabel(actualMethodStartLabel) } - private fun allLambdaParameters() = - if (isSuspendLambda) - originalSuspendFunctionDescriptor.extensionReceiverParameter.singletonOrEmptyList() + originalSuspendFunctionDescriptor.valueParameters.orEmpty() - else - emptyList() - - private fun ExpressionCodegen.generateLoadField(fieldInfo: FieldInfo) { - StackValue.field(fieldInfo, generateThisOrOuter(context.thisDescriptor, false)).put(fieldInfo.fieldType, v) - } + private fun allFunctionParameters() = + originalSuspendFunctionDescriptor.extensionReceiverParameter.singletonOrEmptyList() + + originalSuspendFunctionDescriptor.valueParameters.orEmpty() private fun ParameterDescriptor.getFieldInfoForCoroutineLambdaParameter() = createHiddenFieldInfo(type, COROUTINE_LAMBDA_PARAMETER_PREFIX + (this.safeAs()?.index ?: "")) diff --git a/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec.kt b/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec.kt new file mode 100644 index 00000000000..e2c8cc488dd --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec.kt @@ -0,0 +1,35 @@ +// WITH_RUNTIME +// WITH_COROUTINES +// IGNORE_BACKEND: JS +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* +import kotlin.test.assertEquals + +suspend fun ArrayList.yield(v: Int): Unit = suspendCoroutineOrReturn { x -> + this.add(v) + x.resume(Unit) + COROUTINE_SUSPENDED +} + +tailrec suspend fun ArrayList.fromTo(from: Int, to: Int) { + if (from > to) return + yield(from) + return fromTo(from + 1, to) +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + val result = arrayListOf() + + builder { + result.fromTo(1, 5) + } + + assertEquals(listOf(1, 2, 3, 4, 5), result) + + return "OK" +} + diff --git a/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/tailrec.txt b/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/tailrec.txt new file mode 100644 index 00000000000..04fea26e23a --- /dev/null +++ b/compiler/testData/codegen/light-analysis/coroutines/featureIntersection/tailrec.txt @@ -0,0 +1,32 @@ +@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 TailrecKt { + 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 fromTo(@org.jetbrains.annotations.NotNull p0: java.util.ArrayList, p1: int, p2: int, @org.jetbrains.annotations.NotNull p3: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method yield(@org.jetbrains.annotations.NotNull p0: java.util.ArrayList, p1: int, @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 c34b23342d4..7ae71e33d7c 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 @@ -5042,6 +5042,21 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes } } + @TestMetadata("compiler/testData/codegen/box/coroutines/featureIntersection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class FeatureIntersection extends AbstractIrBlackBoxCodegenTest { + public void testAllFilesPresentInFeatureIntersection() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/featureIntersection"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("tailrec.kt") + public void testTailrec() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/tailrec.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 63fdae54dc4..bd95d34fdc7 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5042,6 +5042,21 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/coroutines/featureIntersection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class FeatureIntersection extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInFeatureIntersection() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/featureIntersection"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("tailrec.kt") + public void testTailrec() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/tailrec.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java index 0521fc8b1c1..0f8eddedcd6 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeCodegenTestGenerated.java @@ -5042,6 +5042,21 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis } } + @TestMetadata("compiler/testData/codegen/box/coroutines/featureIntersection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class FeatureIntersection extends AbstractLightAnalysisModeCodegenTest { + public void testAllFilesPresentInFeatureIntersection() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/featureIntersection"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("tailrec.kt") + public void testTailrec() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/tailrec.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) 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 67a9f8fe5ae..823ce425824 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 @@ -5727,6 +5727,27 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { } } + @TestMetadata("compiler/testData/codegen/box/coroutines/featureIntersection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class FeatureIntersection extends AbstractJsCodegenBoxTest { + public void testAllFilesPresentInFeatureIntersection() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/featureIntersection"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("tailrec.kt") + public void testTailrec() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/featureIntersection/tailrec.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)