Support tailrec suspend functions in JVM backend
The main problem is that inside a state machine for a named suspend function parameters of it's owner are available as a usual captured closure parameters (i.e. through synthetic fields), while TailRecursion codegen expects that parameters are straight local variables. So, the solution is just to define local var for each of real parameters (all but the last continuation parameters) for tailrec functions. #KT-15759 Fixed
This commit is contained in:
@@ -2497,14 +2497,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> 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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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<ValueParameterDescriptor>()?.index ?: ""))
|
||||
|
||||
@@ -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<Int>.yield(v: Int): Unit = suspendCoroutineOrReturn { x ->
|
||||
this.add(v)
|
||||
x.resume(Unit)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
|
||||
tailrec suspend fun ArrayList<Int>.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<Int>()
|
||||
|
||||
builder {
|
||||
result.fromTo(1, 5)
|
||||
}
|
||||
|
||||
assertEquals(listOf(1, 2, 3, 4, 5), result)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
|
||||
+32
@@ -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 <init>(): void
|
||||
public method <init>(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.CoroutineContext): void
|
||||
public synthetic method <init>(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 <init>(): 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
|
||||
}
|
||||
+15
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+15
@@ -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)
|
||||
|
||||
+21
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user