From 78b9c082e786964c1f2b4d7e86b0a6302ce579ed Mon Sep 17 00:00:00 2001 From: Ilmir Usmanov Date: Fri, 11 Oct 2019 21:39:47 +0300 Subject: [PATCH] JVM_IR: Generate 'create' for suspend lambdas with one parameter --- .../kotlin/backend/jvm/JvmSymbols.kt | 22 +++------ .../backend/jvm/codegen/ExpressionCodegen.kt | 1 + .../backend/jvm/codegen/IrTypeMapper.kt | 1 + .../jvm/lower/AddContinuationLowering.kt | 46 +++++++++++-------- .../coroutines/debug/debuggerMetadata_ir.kt | 2 +- .../tailrec/controlFlowIf.kt | 1 - .../tailrec/controlFlowWhen.kt | 1 - .../suspendFunctionTypeCall/simple.kt | 1 - 8 files changed, 37 insertions(+), 38 deletions(-) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index b5ce1b3c725..ce56f5bf310 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -209,36 +209,28 @@ class JvmSymbols( symbolTable.referenceClass(builtIns.getFunction(parameterCount)) private val jvmFunctionClasses = storageManager.createMemoizedFunction { n: Int -> - createClass(FqName("kotlin.jvm.functions.Function$n"), ClassKind.INTERFACE) { klass -> + createFunctionClass(n, false) + } + + private fun createFunctionClass(n: Int, isSuspend: Boolean): IrClassSymbol = + createClass(FqName("kotlin.jvm.functions.Function${n + if (isSuspend) 1 else 0}"), ClassKind.INTERFACE) { klass -> for (i in 1..n) { klass.addTypeParameter("P$i", irBuiltIns.anyNType, Variance.IN_VARIANCE) } val returnType = klass.addTypeParameter("R", irBuiltIns.anyNType, Variance.OUT_VARIANCE) - klass.addFunction("invoke", returnType.defaultType, Modality.ABSTRACT).apply { + klass.addFunction("invoke", returnType.defaultType, Modality.ABSTRACT, isSuspend = isSuspend).apply { for (i in 1..n) { addValueParameter("p$i", klass.typeParameters[i - 1].defaultType) } } } - } fun getJvmFunctionClass(parameterCount: Int): IrClassSymbol = jvmFunctionClasses(parameterCount) private val jvmSuspendFunctionClasses = storageManager.createMemoizedFunction { n: Int -> - createClass(FqName("kotlin.jvm.functions.Function${n + 1}"), ClassKind.INTERFACE) { klass -> - for (i in 1..n) { - klass.addTypeParameter("P$i", irBuiltIns.anyNType, Variance.IN_VARIANCE) - } - val returnType = klass.addTypeParameter("R", irBuiltIns.anyNType, Variance.OUT_VARIANCE) - - klass.addFunction("invoke", returnType.defaultType, Modality.ABSTRACT, isSuspend = true).apply { - for (i in 1..n) { - addValueParameter("p$i", klass.typeParameters[i - 1].defaultType) - } - } - } + createFunctionClass(n, true) } fun getJvmSuspendFunctionClass(parameterCount: Int): IrClassSymbol = diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 271a166f221..5481bb1f7e5 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -152,6 +152,7 @@ class ExpressionCodegen( // State-machine builder splits the sequence of instructions into states inside state-machine, adding additional LINENUMBERs // between them for debugger to stop on suspension. Thus, it requires as much LINENUMBER information as possible to be present, // otherwise, any exception will have incorrect line number. See elvisLineNumber.kt test. + // TODO: Remove unneeded LINENUMBERs after building the state-machine. if (lastLineNumber != lineNumber || irFunction.isSuspend || irFunction.isInvokeSuspendOfLambda(context)) { lastLineNumber = lineNumber mv.visitLineNumber(lineNumber, markNewLabel()) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt index f9d5b43cf1c..371ba60d0fa 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrTypeMapper.kt @@ -35,6 +35,7 @@ import org.jetbrains.org.objectweb.asm.Type class IrTypeMapper(private val context: JvmBackendContext) : KotlinTypeMapperBase() { internal val typeSystem = IrTypeCheckerContext(context.irBuiltIns) + private val IrTypeArgument.adjustedType get() = (this as? IrTypeProjection)?.type ?: context.irBuiltIns.anyNType diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt index e0462444ea0..e133a14e8d5 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt @@ -129,6 +129,9 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : } val parametersFields = info.function.valueParameters.map { addField(it.name.asString(), it.type) } + val parametersWithoutArguments = parametersFields.withIndex() + .mapNotNull { (i, field) -> if (info.reference.getValueArgument(i) == null) field else null } + val parametersWithArguments = parametersFields - parametersWithoutArguments val constructor = addPrimaryConstructorForLambda(info.arity, info.reference, parametersFields) val secondaryConstructor = addSecondaryConstructorForLambda(constructor) val invokeToOverride = functionNClass.functions.single { @@ -136,10 +139,11 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : } val invokeSuspend = addInvokeSuspendForLambda(info.function, parametersFields, receiverField) if (info.arity <= 1) { - val create = addCreate(constructor, suspendLambda, info.arity, parametersFields, receiverField) - addInvoke(create, invokeSuspend, invokeToOverride, parametersFields, receiverField, isConstructorCall = false) + val singleParameterField = receiverField ?: parametersWithoutArguments.singleOrNull() + val create = addCreate(constructor, suspendLambda, info, parametersWithArguments, singleParameterField) + addInvoke(create, invokeSuspend, invokeToOverride, singleParameterField, emptyList(), isConstructorCall = false) } else { - addInvoke(constructor, invokeSuspend, invokeToOverride, parametersFields, receiverField, isConstructorCall = true) + addInvoke(constructor, invokeSuspend, invokeToOverride, receiverField, parametersWithoutArguments, isConstructorCall = true) } context.suspendLambdaToOriginalFunctionMap[this] = info.function @@ -205,8 +209,8 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : create: IrFunction, invokeSuspend: IrFunction, invokeToOverride: IrSimpleFunctionSymbol, - parametersFields: List, receiverField: IrField?, + parametersWithoutArguments: List, isConstructorCall: Boolean ) { val unitClass = context.irBuiltIns.unitClass @@ -222,12 +226,14 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : } createCall.putValueArgument(if (receiverField != null) 1 else 0, irGet(function.valueParameters.last())) }, "create") - // In old BE 'create' function was responsible for putting arguments into fields, but in IR_BE I do not generate create, - // unless suspend lambda has no parameters (extension receiver is allowed, however) - // Thus, we put arguments into fields - if (function.valueParameters.size > create.valueParameters.size) { + // In old BE 'create' function was responsible for putting arguments into fields, but in IR_BE we do not generate create, + // unless suspend lambda has no or one parameter, including extension receiver + // This is because 'create' is called only from 'createCoroutineUnintercepted' from stdlib. And we have only versions + // for suspend lambdas without parameters and for ones with exactly one parameter (or extension receiver). + // Thus, we put arguments into fields in 'invoke'. + if (parametersWithoutArguments.isNotEmpty()) { for ((index, param) in function.valueParameters.drop(if (receiverField != null) 1 else 0).dropLast(1).withIndex()) { - +irSetField(irGet(newlyCreatedObject), parametersFields[index], irGet(param)) + +irSetField(irGet(newlyCreatedObject), parametersWithoutArguments[index], irGet(param)) } } +irReturn(irCall(invokeSuspend).also { invokeSuspendCall -> @@ -241,27 +247,29 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : private fun IrClass.addCreate( constructor: IrFunction, superType: IrClass, - arity: Int, - parametersFields: List, - receiverField: IrField? + info: SuspendLambdaInfo, + parametersWithArguments: List, + singleParameterField: IrField? ): IrFunction { val create = superType.functions.single { - it.name.asString() == "create" && it.valueParameters.size == arity + 1 && + it.name.asString() == "create" && it.valueParameters.size == info.arity + 1 && it.valueParameters.last().type.isContinuation() && - if (arity == 1) it.valueParameters.first().type.isNullableAny() else true + if (info.arity == 1) it.valueParameters.first().type.isNullableAny() else true } return addFunctionOverride(create).also { function -> function.body = context.createIrBuilder(function.symbol).irBlockBody { + var index = 0 val constructorCall = irCall(constructor).also { - for ((i, field) in parametersFields.withIndex()) { - it.putValueArgument(i, irGetField(irGet(function.dispatchReceiverParameter!!), field)) + for ((i, field) in parametersWithArguments.withIndex()) { + if (info.reference.getValueArgument(i) == null) continue + it.putValueArgument(index++, irGetField(irGet(function.dispatchReceiverParameter!!), field)) } - it.putValueArgument(parametersFields.size, irGet(function.valueParameters.last())) + it.putValueArgument(index, irGet(function.valueParameters.last())) } - if (receiverField != null) { + if (singleParameterField != null) { assert(function.valueParameters.size == 2) val result = irTemporary(constructorCall, "result") - +irSetField(irGet(result), receiverField, irGet(function.valueParameters.first())) + +irSetField(irGet(result), singleParameterField, irGet(function.valueParameters.first())) +irReturn(irGet(result)) } else { assert(function.valueParameters.size == 1) diff --git a/compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt b/compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt index be23b6319ba..51db4d719e5 100644 --- a/compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt +++ b/compiler/testData/codegen/box/coroutines/debug/debuggerMetadata_ir.kt @@ -2,6 +2,7 @@ // TARGET_BACKEND: JVM // WITH_RUNTIME // WITH_COROUTINES +// TODO: Merge the test with debuggerMetadata when KT-28016 is fixed, particulary, do not spill 0, since it is constant. @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") @@ -93,7 +94,6 @@ fun box(): String { builder { multipleLocalsInOneSlot() } - // TODO: Do not spill 0, since it is constant. res = (continuation!! as BaseContinuationImpl).getSpilledVariableFieldMapping()!!.toMap()["I$1"] ?: "multipleLocalsInOneSlot fail 1" if (res != "first") { return "" + res diff --git a/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowIf.kt b/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowIf.kt index 5f877bbe67d..58b8f89974e 100644 --- a/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowIf.kt +++ b/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowIf.kt @@ -1,5 +1,4 @@ // KJS_WITH_FULL_RUNTIME -// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME // WITH_COROUTINES // COMMON_COROUTINES_TEST diff --git a/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowWhen.kt b/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowWhen.kt index 4339cf6f7f5..334020019c7 100644 --- a/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowWhen.kt +++ b/compiler/testData/codegen/box/coroutines/featureIntersection/tailrec/controlFlowWhen.kt @@ -1,5 +1,4 @@ // KJS_WITH_FULL_RUNTIME -// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME // WITH_COROUTINES // COMMON_COROUTINES_TEST diff --git a/compiler/testData/codegen/box/coroutines/suspendFunctionTypeCall/simple.kt b/compiler/testData/codegen/box/coroutines/suspendFunctionTypeCall/simple.kt index 0befaa7cb3e..5f8d15e93ba 100644 --- a/compiler/testData/codegen/box/coroutines/suspendFunctionTypeCall/simple.kt +++ b/compiler/testData/codegen/box/coroutines/suspendFunctionTypeCall/simple.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME // COMMON_COROUTINES_TEST // WITH_COROUTINES