JVM KT-47613 custom control flow analyzer for CFG builder

This commit is contained in:
Dmitry Petrov
2021-07-11 08:49:49 +03:00
parent fe71435104
commit 804db3ce91
5 changed files with 187 additions and 37 deletions
@@ -347,8 +347,42 @@ internal val AbstractInsnNode?.insnText: String
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
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 {
//try to find just compiled classes then in dependencies
@@ -5,47 +5,160 @@
package org.jetbrains.kotlin.codegen.optimization.common
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.InsnList
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
import gnu.trove.TIntHashSet
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.*
class ControlFlowGraph private constructor(private val insns: InsnList) {
private val edges: Array<MutableList<Int>> = Array(insns.size()) { ArrayList() }
private val backwardEdges: Array<MutableList<Int>> = Array(insns.size()) { ArrayList() }
private val successors: 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(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(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 {
@JvmStatic
fun build(node: MethodNode, followExceptions: Boolean = true): ControlFlowGraph {
val graph = ControlFlowGraph(node.instructions)
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
return Builder(node, followExceptions).build()
}
}
}
@@ -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.Interpreter
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`
@@ -93,7 +92,7 @@ open class FlexibleMethodAnalyzer<V : Value>(
checkAssertions()
computeExceptionHandlersForEachInsn(method)
computeExceptionHandlersForEachInsn()
initSinglePredBlocks()
@@ -306,8 +305,8 @@ open class FlexibleMethodAnalyzer<V : Value>(
mergeControlFlowEdge(0, 0, current)
}
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
for (tcb in m.tryCatchBlocks) {
private fun computeExceptionHandlersForEachInsn() {
for (tcb in method.tryCatchBlocks) {
val begin = tcb.start.indexOf()
val end = tcb.end.indexOf()
for (j in begin until end) {
@@ -124,7 +124,7 @@ class InstructionLivenessAnalyzer(val method: MethodNode) {
var jump = insnNode.dflt.indexOf
visitControlFlowEdge(jump)
for (label in insnNode.labels) {
jump = instructions.indexOf(label)
jump = label.indexOf
visitControlFlowEdge(jump)
}
}
+4
View File
@@ -27,6 +27,10 @@ the Kotlin IntelliJ IDEA plugin:
- 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: 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
- License: MIT ([license/third_party/pcollections_LICENSE.txt][pcollections])
- Origin: Derived from PCollections, A Persistent Java Collections Library (https://pcollections.org/)