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)) symbolTable.referenceClass(builtIns.getFunction(parameterCount))
private val jvmFunctionClasses = storageManager.createMemoizedFunction { n: Int -> 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) { for (i in 1..n) {
klass.addTypeParameter("P$i", irBuiltIns.anyNType, Variance.IN_VARIANCE) klass.addTypeParameter("P$i", irBuiltIns.anyNType, Variance.IN_VARIANCE)
} }
val returnType = klass.addTypeParameter("R", irBuiltIns.anyNType, Variance.OUT_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) { for (i in 1..n) {
addValueParameter("p$i", klass.typeParameters[i - 1].defaultType) addValueParameter("p$i", klass.typeParameters[i - 1].defaultType)
} }
} }
} }
}
fun getJvmFunctionClass(parameterCount: Int): IrClassSymbol = fun getJvmFunctionClass(parameterCount: Int): IrClassSymbol =
jvmFunctionClasses(parameterCount) jvmFunctionClasses(parameterCount)
private val jvmSuspendFunctionClasses = storageManager.createMemoizedFunction { n: Int -> private val jvmSuspendFunctionClasses = storageManager.createMemoizedFunction { n: Int ->
createClass(FqName("kotlin.jvm.functions.Function${n + 1}"), ClassKind.INTERFACE) { klass -> createFunctionClass(n, true)
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)
}
}
}
} }
fun getJvmSuspendFunctionClass(parameterCount: Int): IrClassSymbol = 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 // 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, // 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. // 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)) { if (lastLineNumber != lineNumber || irFunction.isSuspend || irFunction.isInvokeSuspendOfLambda(context)) {
lastLineNumber = lineNumber lastLineNumber = lineNumber
mv.visitLineNumber(lineNumber, markNewLabel()) mv.visitLineNumber(lineNumber, markNewLabel())
@@ -35,6 +35,7 @@ import org.jetbrains.org.objectweb.asm.Type
class IrTypeMapper(private val context: JvmBackendContext) : KotlinTypeMapperBase() { class IrTypeMapper(private val context: JvmBackendContext) : KotlinTypeMapperBase() {
internal val typeSystem = IrTypeCheckerContext(context.irBuiltIns) internal val typeSystem = IrTypeCheckerContext(context.irBuiltIns)
private val IrTypeArgument.adjustedType private val IrTypeArgument.adjustedType
get() = (this as? IrTypeProjection)?.type ?: context.irBuiltIns.anyNType 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 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 constructor = addPrimaryConstructorForLambda(info.arity, info.reference, parametersFields)
val secondaryConstructor = addSecondaryConstructorForLambda(constructor) val secondaryConstructor = addSecondaryConstructorForLambda(constructor)
val invokeToOverride = functionNClass.functions.single { val invokeToOverride = functionNClass.functions.single {
@@ -136,10 +139,11 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
} }
val invokeSuspend = addInvokeSuspendForLambda(info.function, parametersFields, receiverField) val invokeSuspend = addInvokeSuspendForLambda(info.function, parametersFields, receiverField)
if (info.arity <= 1) { if (info.arity <= 1) {
val create = addCreate(constructor, suspendLambda, info.arity, parametersFields, receiverField) val singleParameterField = receiverField ?: parametersWithoutArguments.singleOrNull()
addInvoke(create, invokeSuspend, invokeToOverride, parametersFields, receiverField, isConstructorCall = false) val create = addCreate(constructor, suspendLambda, info, parametersWithArguments, singleParameterField)
addInvoke(create, invokeSuspend, invokeToOverride, singleParameterField, emptyList(), isConstructorCall = false)
} else { } else {
addInvoke(constructor, invokeSuspend, invokeToOverride, parametersFields, receiverField, isConstructorCall = true) addInvoke(constructor, invokeSuspend, invokeToOverride, receiverField, parametersWithoutArguments, isConstructorCall = true)
} }
context.suspendLambdaToOriginalFunctionMap[this] = info.function context.suspendLambdaToOriginalFunctionMap[this] = info.function
@@ -205,8 +209,8 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
create: IrFunction, create: IrFunction,
invokeSuspend: IrFunction, invokeSuspend: IrFunction,
invokeToOverride: IrSimpleFunctionSymbol, invokeToOverride: IrSimpleFunctionSymbol,
parametersFields: List<IrField>,
receiverField: IrField?, receiverField: IrField?,
parametersWithoutArguments: List<IrField>,
isConstructorCall: Boolean isConstructorCall: Boolean
) { ) {
val unitClass = context.irBuiltIns.unitClass 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())) createCall.putValueArgument(if (receiverField != null) 1 else 0, irGet(function.valueParameters.last()))
}, "create") }, "create")
// In old BE 'create' function was responsible for putting arguments into fields, but in IR_BE I do not generate create, // 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 parameters (extension receiver is allowed, however) // unless suspend lambda has no or one parameter, including extension receiver
// Thus, we put arguments into fields // This is because 'create' is called only from 'createCoroutineUnintercepted' from stdlib. And we have only versions
if (function.valueParameters.size > create.valueParameters.size) { // 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()) { 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 -> +irReturn(irCall(invokeSuspend).also { invokeSuspendCall ->
@@ -241,27 +247,29 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
private fun IrClass.addCreate( private fun IrClass.addCreate(
constructor: IrFunction, constructor: IrFunction,
superType: IrClass, superType: IrClass,
arity: Int, info: SuspendLambdaInfo,
parametersFields: List<IrField>, parametersWithArguments: List<IrField>,
receiverField: IrField? singleParameterField: IrField?
): IrFunction { ): IrFunction {
val create = superType.functions.single { 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() && 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 -> return addFunctionOverride(create).also { function ->
function.body = context.createIrBuilder(function.symbol).irBlockBody { function.body = context.createIrBuilder(function.symbol).irBlockBody {
var index = 0
val constructorCall = irCall(constructor).also { val constructorCall = irCall(constructor).also {
for ((i, field) in parametersFields.withIndex()) { for ((i, field) in parametersWithArguments.withIndex()) {
it.putValueArgument(i, irGetField(irGet(function.dispatchReceiverParameter!!), field)) 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) assert(function.valueParameters.size == 2)
val result = irTemporary(constructorCall, "result") val result = irTemporary(constructorCall, "result")
+irSetField(irGet(result), receiverField, irGet(function.valueParameters.first())) +irSetField(irGet(result), singleParameterField, irGet(function.valueParameters.first()))
+irReturn(irGet(result)) +irReturn(irGet(result))
} else { } else {
assert(function.valueParameters.size == 1) assert(function.valueParameters.size == 1)
@@ -2,6 +2,7 @@
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// WITH_RUNTIME // WITH_RUNTIME
// WITH_COROUTINES // 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") @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER")
@@ -93,7 +94,6 @@ fun box(): String {
builder { builder {
multipleLocalsInOneSlot() multipleLocalsInOneSlot()
} }
// TODO: Do not spill 0, since it is constant.
res = (continuation!! as BaseContinuationImpl).getSpilledVariableFieldMapping()!!.toMap()["I$1"] ?: "multipleLocalsInOneSlot fail 1" res = (continuation!! as BaseContinuationImpl).getSpilledVariableFieldMapping()!!.toMap()["I$1"] ?: "multipleLocalsInOneSlot fail 1"
if (res != "first") { if (res != "first") {
return "" + res return "" + res
@@ -1,5 +1,4 @@
// KJS_WITH_FULL_RUNTIME // KJS_WITH_FULL_RUNTIME
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME // WITH_RUNTIME
// WITH_COROUTINES // WITH_COROUTINES
// COMMON_COROUTINES_TEST // COMMON_COROUTINES_TEST
@@ -1,5 +1,4 @@
// KJS_WITH_FULL_RUNTIME // KJS_WITH_FULL_RUNTIME
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME // WITH_RUNTIME
// WITH_COROUTINES // WITH_COROUTINES
// COMMON_COROUTINES_TEST // COMMON_COROUTINES_TEST
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME // WITH_RUNTIME
// COMMON_COROUTINES_TEST // COMMON_COROUTINES_TEST
// WITH_COROUTINES // WITH_COROUTINES