JVM KT-47613 custom control flow analyzer for CFG builder
This commit is contained in:
@@ -347,8 +347,42 @@ internal val AbstractInsnNode?.insnText: String
|
|||||||
return sw.toString().trim()
|
return sw.toString().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun AbstractInsnNode?.insnText(insnList: InsnList): String {
|
||||||
|
if (this == null) return "<null>"
|
||||||
|
|
||||||
|
fun AbstractInsnNode.indexOf() =
|
||||||
|
insnList.indexOf(this)
|
||||||
|
|
||||||
|
fun LabelNode.labelText() =
|
||||||
|
"L#${this.indexOf()}"
|
||||||
|
|
||||||
|
return when (this) {
|
||||||
|
is LabelNode ->
|
||||||
|
labelText()
|
||||||
|
is JumpInsnNode ->
|
||||||
|
"$insnOpcodeText ${label.labelText()}"
|
||||||
|
is LookupSwitchInsnNode ->
|
||||||
|
"$insnOpcodeText " +
|
||||||
|
this.keys.zip(this.labels).joinToString(prefix = "[", postfix = "]") { (key, label) -> "$key:${label.labelText()}" }
|
||||||
|
is TableSwitchInsnNode ->
|
||||||
|
"$insnOpcodeText " +
|
||||||
|
(min..max).zip(this.labels).joinToString(prefix = "[", postfix = "]") { (key, label) -> "$key:${label.labelText()}" }
|
||||||
|
else ->
|
||||||
|
insnText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal val AbstractInsnNode?.insnOpcodeText: String
|
internal val AbstractInsnNode?.insnOpcodeText: String
|
||||||
get() = if (this == null) "null" else Printer.OPCODES[opcode]
|
get() = when (this) {
|
||||||
|
null -> "null"
|
||||||
|
is LabelNode -> "LABEL"
|
||||||
|
is LineNumberNode -> "LINENUMBER"
|
||||||
|
is FrameNode -> "FRAME"
|
||||||
|
else -> Printer.OPCODES[opcode]
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TryCatchBlockNode.text(insns: InsnList): String =
|
||||||
|
"[${insns.indexOf(start)} .. ${insns.indexOf(end)} -> ${insns.indexOf(handler)}]"
|
||||||
|
|
||||||
internal fun loadClassBytesByInternalName(state: GenerationState, internalName: String): ByteArray {
|
internal fun loadClassBytesByInternalName(state: GenerationState, internalName: String): ByteArray {
|
||||||
//try to find just compiled classes then in dependencies
|
//try to find just compiled classes then in dependencies
|
||||||
|
|||||||
+144
-31
@@ -5,47 +5,160 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.optimization.common
|
package org.jetbrains.kotlin.codegen.optimization.common
|
||||||
|
|
||||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
import gnu.trove.TIntHashSet
|
||||||
import org.jetbrains.org.objectweb.asm.tree.InsnList
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
|
||||||
|
|
||||||
|
|
||||||
class ControlFlowGraph private constructor(private val insns: InsnList) {
|
class ControlFlowGraph private constructor(private val insns: InsnList) {
|
||||||
private val edges: Array<MutableList<Int>> = Array(insns.size()) { ArrayList() }
|
private val successors: Array<MutableList<Int>> = Array(insns.size()) { ArrayList() }
|
||||||
private val backwardEdges: Array<MutableList<Int>> = Array(insns.size()) { ArrayList() }
|
private val predecessors: Array<MutableList<Int>> = Array(insns.size()) { ArrayList() }
|
||||||
|
|
||||||
fun getSuccessorsIndices(insn: AbstractInsnNode): List<Int> = getSuccessorsIndices(insns.indexOf(insn))
|
fun getSuccessorsIndices(insn: AbstractInsnNode): List<Int> = getSuccessorsIndices(insns.indexOf(insn))
|
||||||
fun getSuccessorsIndices(index: Int): List<Int> = edges[index]
|
fun getSuccessorsIndices(index: Int): List<Int> = successors[index]
|
||||||
fun getPredecessorsIndices(insn: AbstractInsnNode): List<Int> = getPredecessorsIndices(insns.indexOf(insn))
|
fun getPredecessorsIndices(insn: AbstractInsnNode): List<Int> = getPredecessorsIndices(insns.indexOf(insn))
|
||||||
fun getPredecessorsIndices(index: Int): List<Int> = backwardEdges[index]
|
fun getPredecessorsIndices(index: Int): List<Int> = predecessors[index]
|
||||||
|
|
||||||
|
private class Builder(
|
||||||
|
private val method: MethodNode,
|
||||||
|
private val followExceptions: Boolean
|
||||||
|
) {
|
||||||
|
private val instructions = method.instructions
|
||||||
|
private val nInsns = instructions.size()
|
||||||
|
|
||||||
|
private val handlers: Array<MutableList<TryCatchBlockNode>?> = arrayOfNulls(nInsns)
|
||||||
|
|
||||||
|
private val queued = BooleanArray(nInsns)
|
||||||
|
private val queue = IntArray(nInsns)
|
||||||
|
private var top = 0
|
||||||
|
|
||||||
|
private val predecessors = Array(nInsns) { TIntHashSet() }
|
||||||
|
|
||||||
|
private val AbstractInsnNode.indexOf get() = instructions.indexOf(this)
|
||||||
|
|
||||||
|
fun build(): ControlFlowGraph {
|
||||||
|
val graph = ControlFlowGraph(method.instructions)
|
||||||
|
if (nInsns == 0) return graph
|
||||||
|
|
||||||
|
checkAssertions()
|
||||||
|
computeExceptionHandlersForEachInsn()
|
||||||
|
initControlFlowAnalysis()
|
||||||
|
traverseCfg()
|
||||||
|
|
||||||
|
for ((i, preds) in predecessors.withIndex()) {
|
||||||
|
for (pred in preds.toArray()) {
|
||||||
|
graph.predecessors[i].add(pred)
|
||||||
|
graph.successors[pred].add(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return graph
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun traverseCfg() {
|
||||||
|
while (top > 0) {
|
||||||
|
val insn = queue[--top]
|
||||||
|
val insnNode = method.instructions[insn]
|
||||||
|
val insnOpcode = insnNode.opcode
|
||||||
|
|
||||||
|
when (insnNode.type) {
|
||||||
|
AbstractInsnNode.LABEL, AbstractInsnNode.LINE, AbstractInsnNode.FRAME ->
|
||||||
|
visitOpInsn(insn)
|
||||||
|
AbstractInsnNode.JUMP_INSN ->
|
||||||
|
visitJumpInsnNode(insnNode as JumpInsnNode, insn, insnOpcode)
|
||||||
|
AbstractInsnNode.LOOKUPSWITCH_INSN ->
|
||||||
|
visitLookupSwitchInsnNode(insn, insnNode as LookupSwitchInsnNode)
|
||||||
|
AbstractInsnNode.TABLESWITCH_INSN ->
|
||||||
|
visitTableSwitchInsnNode(insn, insnNode as TableSwitchInsnNode)
|
||||||
|
else -> {
|
||||||
|
if (insnOpcode != Opcodes.ATHROW && (insnOpcode < Opcodes.IRETURN || insnOpcode > Opcodes.RETURN)) {
|
||||||
|
visitOpInsn(insn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handlers[insn]?.forEach { tcb ->
|
||||||
|
visitExceptionEdge(insn, tcb.handler.indexOf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAssertions() {
|
||||||
|
if (instructions.any { it.opcode == Opcodes.JSR || it.opcode == Opcodes.RET })
|
||||||
|
throw AssertionError("Subroutines are deprecated since Java 6")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitOpInsn(insn: Int) {
|
||||||
|
visitEdge(insn, insn + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitTableSwitchInsnNode(insn: Int, insnNode: TableSwitchInsnNode) {
|
||||||
|
var jump = insnNode.dflt.indexOf
|
||||||
|
visitEdge(insn, jump)
|
||||||
|
for (label in insnNode.labels) {
|
||||||
|
jump = label.indexOf
|
||||||
|
visitEdge(insn, jump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitLookupSwitchInsnNode(insn: Int, insnNode: LookupSwitchInsnNode) {
|
||||||
|
var jump = insnNode.dflt.indexOf
|
||||||
|
visitEdge(insn, jump)
|
||||||
|
for (label in insnNode.labels) {
|
||||||
|
jump = label.indexOf
|
||||||
|
visitEdge(insn, jump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitJumpInsnNode(insnNode: JumpInsnNode, insn: Int, insnOpcode: Int) {
|
||||||
|
if (insnOpcode != Opcodes.GOTO && insnOpcode != Opcodes.JSR) {
|
||||||
|
visitEdge(insn, insn + 1)
|
||||||
|
}
|
||||||
|
val jump = insnNode.label.indexOf
|
||||||
|
visitEdge(insn, jump)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initControlFlowAnalysis() {
|
||||||
|
queued[0] = true
|
||||||
|
queue[top++] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeExceptionHandlersForEachInsn() {
|
||||||
|
for (tcb in method.tryCatchBlocks) {
|
||||||
|
val begin = tcb.start.indexOf
|
||||||
|
val end = tcb.end.indexOf
|
||||||
|
for (j in begin until end) {
|
||||||
|
val insnHandlers = handlers[j]
|
||||||
|
?: ArrayList<TryCatchBlockNode>().also { handlers[j] = it }
|
||||||
|
insnHandlers.add(tcb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitExceptionEdge(from: Int, to: Int) {
|
||||||
|
if (followExceptions) {
|
||||||
|
predecessors[to].add(from)
|
||||||
|
}
|
||||||
|
enqueue(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitEdge(from: Int, to: Int) {
|
||||||
|
predecessors[to].add(from)
|
||||||
|
enqueue(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enqueue(insn: Int) {
|
||||||
|
if (!queued[insn]) {
|
||||||
|
queued[insn] = true
|
||||||
|
queue[top++] = insn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun build(node: MethodNode, followExceptions: Boolean = true): ControlFlowGraph {
|
fun build(node: MethodNode, followExceptions: Boolean = true): ControlFlowGraph {
|
||||||
val graph = ControlFlowGraph(node.instructions)
|
return Builder(node, followExceptions).build()
|
||||||
|
|
||||||
fun addEdge(from: Int, to: Int) {
|
|
||||||
graph.edges[from].add(to)
|
|
||||||
graph.backwardEdges[to].add(from)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO custom analyzer, no need to analyze data flow
|
|
||||||
object : FlexibleMethodAnalyzer<BasicValue>("fake", node, OptimizationBasicInterpreter()) {
|
|
||||||
override fun visitControlFlowEdge(insn: Int, successor: Int): Boolean {
|
|
||||||
addEdge(insn, successor)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitControlFlowExceptionEdge(insn: Int, successor: Int): Boolean {
|
|
||||||
if (followExceptions) {
|
|
||||||
addEdge(insn, successor)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}.analyze()
|
|
||||||
|
|
||||||
return graph
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -54,7 +54,6 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException
|
|||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
|
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
|
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Value
|
import org.jetbrains.org.objectweb.asm.tree.analysis.Value
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is a modified version of `org.objectweb.asm.tree.analysis.Analyzer`
|
* This class is a modified version of `org.objectweb.asm.tree.analysis.Analyzer`
|
||||||
@@ -93,7 +92,7 @@ open class FlexibleMethodAnalyzer<V : Value>(
|
|||||||
|
|
||||||
checkAssertions()
|
checkAssertions()
|
||||||
|
|
||||||
computeExceptionHandlersForEachInsn(method)
|
computeExceptionHandlersForEachInsn()
|
||||||
|
|
||||||
initSinglePredBlocks()
|
initSinglePredBlocks()
|
||||||
|
|
||||||
@@ -306,8 +305,8 @@ open class FlexibleMethodAnalyzer<V : Value>(
|
|||||||
mergeControlFlowEdge(0, 0, current)
|
mergeControlFlowEdge(0, 0, current)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
|
private fun computeExceptionHandlersForEachInsn() {
|
||||||
for (tcb in m.tryCatchBlocks) {
|
for (tcb in method.tryCatchBlocks) {
|
||||||
val begin = tcb.start.indexOf()
|
val begin = tcb.start.indexOf()
|
||||||
val end = tcb.end.indexOf()
|
val end = tcb.end.indexOf()
|
||||||
for (j in begin until end) {
|
for (j in begin until end) {
|
||||||
|
|||||||
+1
-1
@@ -124,7 +124,7 @@ class InstructionLivenessAnalyzer(val method: MethodNode) {
|
|||||||
var jump = insnNode.dflt.indexOf
|
var jump = insnNode.dflt.indexOf
|
||||||
visitControlFlowEdge(jump)
|
visitControlFlowEdge(jump)
|
||||||
for (label in insnNode.labels) {
|
for (label in insnNode.labels) {
|
||||||
jump = instructions.indexOf(label)
|
jump = label.indexOf
|
||||||
visitControlFlowEdge(jump)
|
visitControlFlowEdge(jump)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ the Kotlin IntelliJ IDEA plugin:
|
|||||||
- License: BSD ([license/third_party/asm_license.txt][asm])
|
- License: BSD ([license/third_party/asm_license.txt][asm])
|
||||||
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
|
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
|
||||||
|
|
||||||
|
- Path: compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt
|
||||||
|
- License: BSD ([license/third_party/asm_license.txt][asm])
|
||||||
|
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
|
||||||
|
|
||||||
- Path: core/reflection.jvm/src/kotlin.reflect/jvm/internal/pcollections
|
- Path: core/reflection.jvm/src/kotlin.reflect/jvm/internal/pcollections
|
||||||
- License: MIT ([license/third_party/pcollections_LICENSE.txt][pcollections])
|
- License: MIT ([license/third_party/pcollections_LICENSE.txt][pcollections])
|
||||||
- Origin: Derived from PCollections, A Persistent Java Collections Library (https://pcollections.org/)
|
- Origin: Derived from PCollections, A Persistent Java Collections Library (https://pcollections.org/)
|
||||||
|
|||||||
Reference in New Issue
Block a user