diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt index 3d654c27539..7452ee725de 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt @@ -143,7 +143,8 @@ class CoroutineTransformerMethodVisitor( val continuationLabels = suspensionPoints.withIndex().map { transformCallAndReturnContinuationLabel( - it.index + 1, it.value, methodNode, suspendMarkerVarIndex, suspensionPointLineNumbers[it.index]) + it.index + 1, it.value, methodNode, suspendMarkerVarIndex, suspensionPointLineNumbers[it.index] + ) } methodNode.instructions.apply { @@ -193,7 +194,7 @@ class CoroutineTransformerMethodVisitor( private fun addCompletionParameterToLVT(methodNode: MethodNode) { val index = - /* all args */ Type.getMethodType(methodNode.desc).argumentTypes.fold(0) { a, b -> a + b.size } + + /* all args */ Type.getMethodType(methodNode.desc).argumentTypes.fold(0) { a, b -> a + b.size } + /* this */ (if (isStatic(methodNode.access)) 0 else 1) - /* only last */ 1 val startLabel = with(methodNode.instructions) { @@ -572,11 +573,15 @@ class CoroutineTransformerMethodVisitor( fun AbstractInsnNode.index() = instructions.indexOf(this) - // We postpone these actions because they change instruction indices that we use when obtaining frames - val postponedActions = mutableListOf<() -> Unit>() val maxVarsCountByType = mutableMapOf() val livenessFrames = analyzeLiveness(methodNode) - val spilledToVariableMapping = arrayListOf>() + + // References shall be cleaned up after uspill (during spill in next suspension point) to prevent memory leaks, + val referencesToSpillBySuspensionPointIndex = arrayListOf>() + // while primitives shall not + val primitivesToSpillBySuspensionPointIndex = arrayListOf>() + + // Collect information about spillable variables, that we use to determine which variables we need to cleanup for (suspension in suspensionPoints) { val suspensionCallBegin = suspension.suspensionCallBegin @@ -603,7 +608,8 @@ class CoroutineTransformerMethodVisitor( // NB: it's also rather useful for sake of optimization val livenessFrame = livenessFrames[suspensionCallBegin.index()] - val spilledToVariable = arrayListOf() + val referencesToSpill = arrayListOf>() + val primitivesToSpill = arrayListOf>() // 0 - this // 1 - parameter @@ -611,68 +617,91 @@ class CoroutineTransformerMethodVisitor( // k - continuation // k + 1 - data // k + 2 - exception - val variablesToSpill = arrayListOf>() for (slot in 0 until localsCount) { if (slot == continuationIndex || slot == dataIndex || slot == exceptionIndex) continue val value = frame.getLocal(slot) if (value.type == null || !livenessFrame.isAlive(slot)) continue - variablesToSpill += slot to value - } - for ((index, basicValue) in variablesToSpill) { - if (basicValue == StrictBasicValue.NULL_VALUE) { - postponedActions.add { - with(instructions) { - insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter { - aconst(null) - store(index, AsmTypes.OBJECT_TYPE) - }) - } - } + if (value == StrictBasicValue.NULL_VALUE) { + referencesToSpill += slot to null continue } - val type = basicValue.type!! + val type = value.type!! val normalizedType = type.normalize() val indexBySort = varsCountByType[normalizedType]?.plus(1) ?: 0 varsCountByType[normalizedType] = indexBySort val fieldName = normalizedType.fieldNameForVar(indexBySort) - localVariableName(methodNode, index, suspension.suspensionCallEnd.next.index()) - ?.let { spilledToVariable.add(SpilledVariableAndField(fieldName, it)) } - - postponedActions.add { - with(instructions) { - // store variable before suspension call - insertBefore(suspension.suspensionCallBegin, withInstructionAdapter { - load(continuationIndex, AsmTypes.OBJECT_TYPE) - load(index, type) - StackValue.coerce(type, normalizedType, this) - putfield(classBuilderForCoroutineState.thisName, fieldName, normalizedType.descriptor) - }) - - // restore variable after suspension call - insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter { - load(continuationIndex, AsmTypes.OBJECT_TYPE) - getfield(classBuilderForCoroutineState.thisName, fieldName, normalizedType.descriptor) - StackValue.coerce(normalizedType, type, this) - store(index, type) - }) - } + if (normalizedType == AsmTypes.OBJECT_TYPE) { + referencesToSpill += slot to SpillableVariable(value, type, normalizedType, fieldName) + } else { + primitivesToSpill += slot to SpillableVariable(value, type, normalizedType, fieldName) } } - spilledToVariableMapping.add(spilledToVariable) + referencesToSpillBySuspensionPointIndex += referencesToSpill + primitivesToSpillBySuspensionPointIndex += primitivesToSpill - varsCountByType.forEach { - maxVarsCountByType[it.key] = max(maxVarsCountByType[it.key] ?: 0, it.value) + for ((type, index) in varsCountByType) { + maxVarsCountByType[type] = max(maxVarsCountByType[type] ?: 0, index) } } - postponedActions.forEach(Function0::invoke) + // TODO: Calculate variable to cleaup - maxVarsCountByType.forEach { entry -> + // Mutate method node + + fun generateSpillAndUnspill(suspension: SuspensionPoint, slot: Int, spillableVariable: SpillableVariable?) { + if (spillableVariable == null) { + with(instructions) { + insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter { + aconst(null) + store(slot, AsmTypes.OBJECT_TYPE) + }) + } + return + } + + with(instructions) { + // store variable before suspension call + insertBefore(suspension.suspensionCallBegin, withInstructionAdapter { + load(continuationIndex, AsmTypes.OBJECT_TYPE) + load(slot, spillableVariable.type) + StackValue.coerce(spillableVariable.type, spillableVariable.normalizedType, this) + putfield( + classBuilderForCoroutineState.thisName, + spillableVariable.fieldName, + spillableVariable.normalizedType.descriptor + ) + }) + + // restore variable after suspension call + insert(suspension.tryCatchBlockEndLabelAfterSuspensionCall, withInstructionAdapter { + load(continuationIndex, AsmTypes.OBJECT_TYPE) + getfield( + classBuilderForCoroutineState.thisName, + spillableVariable.fieldName, + spillableVariable.normalizedType.descriptor + ) + StackValue.coerce(spillableVariable.normalizedType, spillableVariable.type, this) + store(slot, spillableVariable.type) + }) + } + } + + for (suspensionPointIndex in suspensionPoints.indices) { + val suspension = suspensionPoints[suspensionPointIndex] + for ((slot, referenceToSpill) in referencesToSpillBySuspensionPointIndex[suspensionPointIndex]) { + generateSpillAndUnspill(suspension, slot, referenceToSpill) + } + for ((slot, primitiveToSpill) in primitivesToSpillBySuspensionPointIndex[suspensionPointIndex]) { + generateSpillAndUnspill(suspension, slot, primitiveToSpill) + } + } + + for (entry in maxVarsCountByType) { val (type, maxIndex) = entry for (index in 0..maxIndex) { classBuilderForCoroutineState.newField( @@ -681,6 +710,34 @@ class CoroutineTransformerMethodVisitor( ) } } + + // Calculate debug metadata mapping + + fun calculateSpilledVariableAndField( + suspension: SuspensionPoint, + slot: Int, + spillableVariable: SpillableVariable? + ): SpilledVariableAndField? { + if (spillableVariable == null) return null + val name = localVariableName(methodNode, slot, suspension.suspensionCallEnd.next.index()) ?: return null + return SpilledVariableAndField(spillableVariable.fieldName, name) + } + + val spilledToVariableMapping = arrayListOf>() + for (suspensionPointIndex in suspensionPoints.indices) { + val suspension = suspensionPoints[suspensionPointIndex] + + val spilledToVariable = arrayListOf() + + referencesToSpillBySuspensionPointIndex[suspensionPointIndex].mapNotNullTo(spilledToVariable) { (slot, spillableVariable) -> + calculateSpilledVariableAndField(suspension, slot, spillableVariable) + } + primitivesToSpillBySuspensionPointIndex[suspensionPointIndex].mapNotNullTo(spilledToVariable) { (slot, spillableVariable) -> + calculateSpilledVariableAndField(suspension, slot, spillableVariable) + } + + spilledToVariableMapping += spilledToVariable + } return spilledToVariableMapping } @@ -864,6 +921,16 @@ class CoroutineTransformerMethodVisitor( private data class SpilledVariableAndField(val fieldName: String, val variableName: String) } +private class SpillableVariable( + val value: BasicValue, + val type: Type, + val normalizedType: Type, + val fieldName: String +) + +private typealias ReferenceToSpill = Pair +private typealias PrimitiveToSpill = Pair + internal fun InstructionAdapter.generateContinuationConstructorCall( objectTypeForState: Type?, methodNode: MethodNode, diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/TailCallOptimization.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/TailCallOptimization.kt index 93b2b1486d9..c1b6276751e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/TailCallOptimization.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/TailCallOptimization.kt @@ -26,7 +26,6 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue import org.jetbrains.org.objectweb.asm.tree.analysis.Frame -// TODO Use this in variable liveness analysis internal class MethodNodeExaminer( val languageVersionSettings: LanguageVersionSettings, containingClassInternalName: String,