JVM_IR: Generate 'create' for suspend lambdas with one parameter

This commit is contained in:
Ilmir Usmanov
2019-10-11 21:39:47 +03:00
parent 5eeb2958f7
commit 78b9c082e7
8 changed files with 37 additions and 38 deletions
@@ -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 =
@@ -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())
@@ -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
@@ -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<IrField>,
receiverField: IrField?,
parametersWithoutArguments: List<IrField>,
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<IrField>,
receiverField: IrField?
info: SuspendLambdaInfo,
parametersWithArguments: List<IrField>,
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)
@@ -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
@@ -1,5 +1,4 @@
// KJS_WITH_FULL_RUNTIME
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME
// WITH_COROUTINES
// COMMON_COROUTINES_TEST
@@ -1,5 +1,4 @@
// KJS_WITH_FULL_RUNTIME
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME
// WITH_COROUTINES
// COMMON_COROUTINES_TEST
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME
// COMMON_COROUTINES_TEST
// WITH_COROUTINES