Fix range-based 'for' loop with 'continue' in range bounds

1. Search for increment function in range element type, not in inferred
induction variable type
(which can be inappropriate, e.g., 'Nothing' in case of 'continue').

2. Handle nested loops with shared exit labels
(generated by JVM_IR for KT-37370 case).

KT-37370 KT-37373
This commit is contained in:
Dmitry Petrov
2020-03-16 11:40:35 +03:00
parent d5b65abc5d
commit 6809f4439c
14 changed files with 188 additions and 19 deletions
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.codegen.inline.isMarkedReturn
import org.jetbrains.kotlin.codegen.optimization.common.MethodAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode
@@ -44,28 +45,43 @@ internal class FixStackAnalyzer(
const val DEAD_CODE_STACK_SIZE = -1
}
private val expectedStackNode = hashMapOf<LabelNode, AbstractInsnNode>()
private val loopEntryPointMarkers = hashMapOf<LabelNode, SmartList<AbstractInsnNode>>()
val maxExtraStackSize: Int get() = analyzer.maxExtraStackSize
fun getStackToSpill(location: AbstractInsnNode) = analyzer.spilledStacks[location]
fun getActualStack(location: AbstractInsnNode) = getFrame(location)?.getStackContent()
fun getActualStackSize(location: AbstractInsnNode) = getFrame(location)?.stackSizeWithExtra ?: DEAD_CODE_STACK_SIZE
fun getExpectedStackSize(location: AbstractInsnNode) = getExpectedStackFrame(location)?.stackSizeWithExtra ?: DEAD_CODE_STACK_SIZE
private fun getExpectedStackFrame(location: AbstractInsnNode) = getFrame(expectedStackNode[location] ?: location)
fun getExpectedStackSize(location: AbstractInsnNode): Int {
// We should look for expected stack size at loop entry point markers if available,
// otherwise at location itself.
val expectedStackSizeNodes = loopEntryPointMarkers[location] ?: listOf(location)
// Find 1st live node among expected stack size nodes and return corresponding stack size
for (node in expectedStackSizeNodes) {
val frame = getFrame(node) ?: continue
return frame.stackSizeWithExtra
}
// No live nodes found
// => loop entry point is unreachable or node itself is unreachable
return DEAD_CODE_STACK_SIZE
}
private fun getFrame(location: AbstractInsnNode) = analyzer.getFrame(location) as? InternalAnalyzer.FixStackFrame
fun analyze() {
preprocess()
recordLoopEntryPointMarkers()
analyzer.analyze()
}
private fun preprocess() {
private fun recordLoopEntryPointMarkers() {
// NB JVM_IR can generate nested loops with same exit labels (see kt37370.kt)
for (marker in context.fakeAlwaysFalseIfeqMarkers) {
val next = marker.next
if (next is JumpInsnNode) {
expectedStackNode[next.label] = marker
loopEntryPointMarkers.getOrPut(next.label) { SmartList() }.add(marker)
}
}
}
@@ -106,7 +106,7 @@ class FixStackMethodTransformer : MethodTransformer() {
"Label at $labelIndex, jump at $gotoIndex: stack underflow: $expectedStackSize > $actualStackSize"
}
val actualStackContent = analyzer.getActualStack(gotoNode)
?: throw AssertionError("Jump at $gotoIndex should be alive")
?: throw AssertionError("Jump at $gotoIndex should be alive")
actions.add { replaceMarkerWithPops(methodNode, gotoNode.previous, expectedStackSize, actualStackContent) }
} else if (actualStackSize >= 0 && expectedStackSize < 0) {
throw AssertionError("Live jump $gotoIndex to dead label $labelIndex")