Replace ALOAD 0 with fake continuation if suspension point is default

method

 #KT-25922 Fixed
 #KT-26608 Fixed
 #KT-26658 Fixed
This commit is contained in:
Ilmir Usmanov
2018-09-14 21:53:38 +03:00
parent 85bdb8b787
commit 7b1c564b87
6 changed files with 276 additions and 14 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.codegen.inline.FieldRemapper.Companion.foldName
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.ControlFlowGraph
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
@@ -23,7 +24,7 @@ import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.isInlineClassType
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.types.KotlinType
@@ -640,26 +641,78 @@ class MethodInliner(
private fun replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode: MethodNode) {
val lambdaInfo = inliningContext.lambdaInfo ?: return
if (!lambdaInfo.invokeMethodDescriptor.isSuspend) return
val sources = analyzeMethodNodeBeforeInline(processingNode)
val cfg = ControlFlowGraph.build(processingNode)
val aload0s = processingNode.instructions.asSequence().filter { it.opcode == Opcodes.ALOAD && it.safeAs<VarInsnNode>()?.`var` == 0 }
// Expected pattern here:
// ALOAD 0
// ICONST_0
// INVOKESTATIC InlineMarker.mark
// INVOKE* suspendingFunction(..., Continuation;)Ljava/lang/Object;
val continuationAsParameterAload0s =
aload0s.filter { it.next?.next?.let(::isBeforeSuspendMarker) == true && isSuspendCall(it.next?.next?.next) }
replaceContinuationsWithFakeOnes(continuationAsParameterAload0s, processingNode)
val visited = hashSetOf<AbstractInsnNode>()
fun findMeaningfulSuccs(insn: AbstractInsnNode): Collection<AbstractInsnNode> {
if (!visited.add(insn)) return emptySet()
val res = hashSetOf<AbstractInsnNode>()
for (succIndex in cfg.getSuccessorsIndices(insn)) {
val succ = processingNode.instructions[succIndex]
if (succ.isMeaningful) res.add(succ)
else res.addAll(findMeaningfulSuccs(succ))
}
return res
}
// After inlining suspendCoroutineUninterceptedOrReturn there will be suspension point, which is not a MethodInsnNode.
// So, it is incorrect to expect MethodInsnNodes only
val suspensionPoints = processingNode.instructions.asSequence()
.filter { isBeforeSuspendMarker(it) }
.flatMap { findMeaningfulSuccs(it).asSequence() }
.filter { it is MethodInsnNode }
val toReplace = hashSetOf<AbstractInsnNode>()
for (suspensionPoint in suspensionPoints) {
assert(suspensionPoint is MethodInsnNode) {
"suspensionPoint shall be MethodInsnNode, but instead $suspensionPoint"
}
suspensionPoint as MethodInsnNode
assert(Type.getReturnType(suspensionPoint.desc) == OBJECT_TYPE) {
"suspensionPoint shall return $OBJECT_TYPE, but returns ${Type.getReturnType(suspensionPoint.desc)}"
}
val frame = sources[processingNode.instructions.indexOf(suspensionPoint)] ?: continue
val paramTypes = Type.getArgumentTypes(suspensionPoint.desc)
if (suspensionPoint.name.endsWith(JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX)) {
// Expected pattern here:
// ALOAD 0
// (ICONST or other integers creating instruction)
// (ACONST_NULL or ALOAD)
// ICONST_0
// INVOKESTATIC InlineMarker.mark
// INVOKE* suspendingFunction$default(..., Continuation;ILjava/lang/Object)Ljava/lang/Object;
assert(paramTypes.size >= 3) {
"${suspensionPoint.name}${suspensionPoint.desc} shall have 3+ parameters"
}
} else {
// Expected pattern here:
// ALOAD 0
// ICONST_0
// INVOKESTATIC InlineMarker.mark
// INVOKE* suspendingFunction(..., Continuation;)Ljava/lang/Object;
assert(paramTypes.isNotEmpty()) {
"${suspensionPoint.name}${suspensionPoint.desc} shall have 1+ parameters"
}
}
paramTypes.reversed().asSequence().withIndex()
.filter { it.value == languageVersionSettings.continuationAsmType() || it.value == OBJECT_TYPE }
.flatMap { frame.getStack(frame.stackSize - it.index - 1).insns.asSequence() }
.filter { it in aload0s }.let { toReplace.addAll(it) }
}
// Expected pattern here:
// ALOAD 0
// ASTORE N
// This pattern may occur after multiple inlines
val continuationToStoreAload0s = aload0s.filter { it.next?.opcode == Opcodes.ASTORE }
replaceContinuationsWithFakeOnes(continuationToStoreAload0s, processingNode)
// Note, that this is not a suspension point, thus we check it separately
toReplace.addAll(aload0s.filter { it.next?.opcode == Opcodes.ASTORE })
// Expected pattern here:
// ALOAD 0
// INVOKEINTERFACE kotlin/jvm/functions/FunctionN.invoke (...,Ljava/lang/Object;)Ljava/lang/Object;
val continuationAsLambdaParameterAload0s = aload0s.filter { isLambdaCall(it.next) }
replaceContinuationsWithFakeOnes(continuationAsLambdaParameterAload0s, processingNode)
toReplace.addAll(aload0s.filter { isLambdaCall(it.next) })
replaceContinuationsWithFakeOnes(toReplace, processingNode)
}
private fun isLambdaCall(invoke: AbstractInsnNode?): Boolean {
@@ -672,7 +725,7 @@ class MethodInliner(
}
private fun replaceContinuationsWithFakeOnes(
continuations: Sequence<AbstractInsnNode>,
continuations: Collection<AbstractInsnNode>,
node: MethodNode
) {
for (toReplace in continuations) {