Support jump out of the constructor call in suspend functions

Jump out from expression (e.g., break or continue expression in call
arguments) requires stack normalization, which inserts POP instructions.
POPping an uninitialized value is similar to ASTORE, except that it
doesn't store a value to a local variable. Such POP instructions should
be removed during postprocessing of the uninitialized stores.
This commit is contained in:
Dmitry Petrov
2017-10-03 10:22:53 +03:00
parent da6841163b
commit c3d74bdabb
6 changed files with 215 additions and 7 deletions
@@ -19,6 +19,8 @@ package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.codegen.inline.insnText
import org.jetbrains.kotlin.codegen.optimization.common.*
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
import org.jetbrains.kotlin.codegen.optimization.fixStack.peekWords
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.*
@@ -90,19 +92,21 @@ class UninitializedStoresProcessor(private val methodNode: MethodNode) {
this::UninitializedNewValueFrame
).analyze()
interpreter.analyzePopInstructions(frames)
for ((index, insn) in methodNode.instructions.toArray().withIndex()) {
val frame = frames[index] ?: continue
val uninitializedValue = frame.getUninitializedValueForConstructorCall(insn) ?: continue
val newInsn = uninitializedValue.newInsn
val copyUsages: Set<AbstractInsnNode> = interpreter.uninitializedValuesToCopyUsages[newInsn]!!
assert(copyUsages.isNotEmpty()) { "At least DUP copy operation expected" }
val removableUsages: Set<AbstractInsnNode> = interpreter.uninitializedValuesToRemovableUsages[newInsn]!!
assert(removableUsages.isNotEmpty()) { "At least DUP copy operation expected" }
// Value generated by NEW wasn't store to local/field (only DUPed)
if (copyUsages.size == 1) continue
if (removableUsages.size == 1) continue
methodNode.instructions.run {
removeAll(copyUsages)
removeAll(removableUsages)
// Replace 'NEW C' instruction with "manual" initialization of class 'C':
// LDC [typeName for C]
@@ -194,10 +198,10 @@ class UninitializedStoresProcessor(private val methodNode: MethodNode) {
private fun AbstractInsnNode.isConstructorCall() = this is MethodInsnNode && this.name == "<init>"
private class UninitializedNewValueMarkerInterpreter(private val instructions: InsnList) : OptimizationBasicInterpreter() {
val uninitializedValuesToCopyUsages = hashMapOf<AbstractInsnNode, MutableSet<AbstractInsnNode>>()
val uninitializedValuesToRemovableUsages = hashMapOf<AbstractInsnNode, MutableSet<AbstractInsnNode>>()
override fun newOperation(insn: AbstractInsnNode): BasicValue? {
if (insn.opcode == Opcodes.NEW) {
uninitializedValuesToCopyUsages.getOrPut(insn) { mutableSetOf() }
uninitializedValuesToRemovableUsages.getOrPut(insn) { mutableSetOf() }
return UninitializedNewValue(insn as TypeInsnNode, insn.desc)
}
return super.newOperation(insn)
@@ -206,7 +210,7 @@ class UninitializedStoresProcessor(private val methodNode: MethodNode) {
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? {
if (value is UninitializedNewValue) {
checkUninitializedObjectCopy(value.newInsn, insn)
uninitializedValuesToCopyUsages[value.newInsn]!!.add(insn)
uninitializedValuesToRemovableUsages[value.newInsn]!!.add(insn)
return value
}
return super.copyOperation(insn, value)
@@ -239,5 +243,33 @@ class UninitializedStoresProcessor(private val methodNode: MethodNode) {
private val AbstractInsnNode.debugText
get() = "${instructions.indexOf(this)}: $insnText"
fun analyzePopInstructions(frames: Array<Frame<BasicValue>?>) {
val insns = instructions.toArray()
for (i in frames.indices) {
val frame = frames[i] ?: continue
val insn = insns[i]
when (insn.opcode) {
Opcodes.POP -> analyzePop(insn, frame)
Opcodes.POP2 -> analyzePop2(insn, frame)
}
}
}
private fun analyzePop(insn: AbstractInsnNode, frame: Frame<BasicValue>) {
val top = frame.top() ?: error("Stack underflow on POP: ${insn.debugText}")
if (top is UninitializedNewValue) {
uninitializedValuesToRemovableUsages[top.newInsn]!!.add(insn)
}
}
private fun analyzePop2(insn: AbstractInsnNode, frame: Frame<BasicValue>) {
val top2 = frame.peekWords(2) ?: error("Stack underflow on POP2: ${insn.debugText}")
for (value in top2) {
if (value is UninitializedNewValue) {
error("Unexpected POP2 instruction for ${value.newInsn.debugText}: ${insn.debugText}")
}
}
}
}
}