KT-17110 Rewrite branches targeting other branches

- Rewrite branches targeting other branches to target directly the
final destination to avoid runtime performance penalties.

Fix of https://youtrack.jetbrains.com/issue/KT-17110
This commit is contained in:
Mikaël Peltier
2018-02-09 15:40:04 +01:00
committed by Dmitry Petrov
parent c9624ce0f9
commit 9fb0f59813
8 changed files with 133 additions and 8 deletions
@@ -22,31 +22,76 @@ import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode
import org.jetbrains.org.objectweb.asm.tree.LabelNode
import org.jetbrains.org.objectweb.asm.tree.LineNumberNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class RedundantGotoMethodTransformer : MethodTransformer() {
/**
* Removes redundant GOTO's, i.e. to subsequent labels
* Removes redundant GOTO's in the following cases:
* (1) subsequent labels
* ...
* goto Label (can be removed)
* Label:
* ...
* (2) indirect goto
* ...
* <branch instruction> Label (can be rewrote to <branch instruction> Label2)
* ...
* Label:
* goto Label2 (must not be removed due to the previous instruction that can fallthrough on this goto)
* ...
*/
override fun transform(internalClassName: String, methodNode: MethodNode) {
val insns = methodNode.instructions.toArray().apply { reverse() }
val insnsToRemove = arrayListOf<AbstractInsnNode>()
var insnsToRemove = arrayListOf<AbstractInsnNode>()
val currentLabels = hashSetOf<LabelNode>()
var labelsToReplace = hashMapOf<LabelNode, JumpInsnNode>()
var pendingGoto: JumpInsnNode? = null
for (insn in insns) {
when {
insn is LabelNode ->
insn is LabelNode -> {
currentLabels.add(insn)
insn.opcode == Opcodes.GOTO &&
(insn as JumpInsnNode).label in currentLabels ->
insnsToRemove.add(insn)
insn.isMeaningful ->
pendingGoto?.let { labelsToReplace[insn] = it }
}
insn.opcode == Opcodes.GOTO -> {
pendingGoto = insn as JumpInsnNode
if (insn.label in currentLabels) {
insnsToRemove.add(insn)
} else {
currentLabels.clear()
}
}
insn is LineNumberNode -> pendingGoto = null
insn.isMeaningful -> {
currentLabels.clear()
pendingGoto = null
}
}
}
// Rewrite branch instructions.
if (!labelsToReplace.isEmpty()) {
insns.filter { it is JumpInsnNode }.forEach { rewriteLabelIfNeeded(it as JumpInsnNode, labelsToReplace) }
}
for (insnToRemove in insnsToRemove) {
methodNode.instructions.remove(insnToRemove)
}
}
private fun rewriteLabelIfNeeded(
jumpInsn: JumpInsnNode,
labelsToReplace: HashMap<LabelNode, JumpInsnNode>
) {
getLastTargetJumpInsn(jumpInsn, labelsToReplace).takeIf { it.label != jumpInsn.label }?.let {
// Do not remove the old label because it can be used to define a local variable range.
jumpInsn.label = it.label
}
}
private fun getLastTargetJumpInsn(jumpInsn: JumpInsnNode, labelsToReplace: HashMap<LabelNode, JumpInsnNode>): JumpInsnNode {
labelsToReplace[jumpInsn.label]?.let { return getLastTargetJumpInsn(it, labelsToReplace) }
return jumpInsn
}
}