[devirtualization] Used bag of edges graph representation
Also used handwritten simple hash table for bag of edges instead of HashSet<..>
This commit is contained in:
+193
-62
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
|||||||
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
||||||
import org.jetbrains.kotlin.backend.konan.*
|
import org.jetbrains.kotlin.backend.konan.*
|
||||||
import org.jetbrains.kotlin.backend.konan.util.IntArrayList
|
import org.jetbrains.kotlin.backend.konan.util.IntArrayList
|
||||||
|
import org.jetbrains.kotlin.backend.konan.util.LongArrayList
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
@@ -148,9 +149,6 @@ internal object Devirtualization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed class Node(val id: Int) {
|
sealed class Node(val id: Int) {
|
||||||
var directEdges: IntHashSet? = null
|
|
||||||
var reversedEdges: IntArrayList? = null
|
|
||||||
|
|
||||||
var directCastEdges: MutableList<CastEdge>? = null
|
var directCastEdges: MutableList<CastEdge>? = null
|
||||||
var reversedCastEdges: MutableList<CastEdge>? = null
|
var reversedCastEdges: MutableList<CastEdge>? = null
|
||||||
|
|
||||||
@@ -163,14 +161,6 @@ internal object Devirtualization {
|
|||||||
|
|
||||||
val multiNodeSize get() = multiNodeEnd - multiNodeStart
|
val multiNodeSize get() = multiNodeEnd - multiNodeStart
|
||||||
|
|
||||||
fun addEdge(node: Node) {
|
|
||||||
if (directEdges == null) directEdges = IntHashSet()
|
|
||||||
if (directEdges!!.add(node.id)) {
|
|
||||||
if (node.reversedEdges == null) node.reversedEdges = IntArrayList()
|
|
||||||
node.reversedEdges!!.add(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addCastEdge(edge: CastEdge) {
|
fun addCastEdge(edge: CastEdge) {
|
||||||
if (directCastEdges == null) directCastEdges = ArrayList(1)
|
if (directCastEdges == null) directCastEdges = ArrayList(1)
|
||||||
directCastEdges!!.add(edge)
|
directCastEdges!!.add(edge)
|
||||||
@@ -251,6 +241,11 @@ internal object Devirtualization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun IntArray.forEachEdge(v: Int, block: (Int) -> Unit) {
|
||||||
|
for (i in this[v] until this[v + 1])
|
||||||
|
block(this[i])
|
||||||
|
}
|
||||||
|
|
||||||
inner class TypeHierarchy(val allTypes: Array<DataFlowIR.Type.Declared>) {
|
inner class TypeHierarchy(val allTypes: Array<DataFlowIR.Type.Declared>) {
|
||||||
private val typesSubTypes = Array(allTypes.size) { mutableListOf<DataFlowIR.Type.Declared>() }
|
private val typesSubTypes = Array(allTypes.size) { mutableListOf<DataFlowIR.Type.Declared>() }
|
||||||
private val allInheritors = Array(allTypes.size) { BitSet() }
|
private val allInheritors = Array(allTypes.size) { BitSet() }
|
||||||
@@ -296,7 +291,7 @@ internal object Devirtualization {
|
|||||||
|
|
||||||
fun BitSet.copy() = BitSet(this.size()).apply { this.or(this@copy) }
|
fun BitSet.copy() = BitSet(this.size()).apply { this.or(this@copy) }
|
||||||
|
|
||||||
fun printPathToType(node: Node, type: Int) {
|
fun printPathToType(reversedEdges: IntArray, node: Node, type: Int) {
|
||||||
val nodes = constraintGraph.nodes
|
val nodes = constraintGraph.nodes
|
||||||
val visited = BitSet()
|
val visited = BitSet()
|
||||||
val prev = mutableMapOf<Node, Node>()
|
val prev = mutableMapOf<Node, Node>()
|
||||||
@@ -308,20 +303,21 @@ internal object Devirtualization {
|
|||||||
val prevFront = front
|
val prevFront = front
|
||||||
front = mutableListOf()
|
front = mutableListOf()
|
||||||
for (from in prevFront) {
|
for (from in prevFront) {
|
||||||
val reversedEdges = from.reversedEdges
|
var endBfs = false
|
||||||
if (reversedEdges != null)
|
reversedEdges.forEachEdge(from.id) { toId ->
|
||||||
for (toId in reversedEdges) {
|
val to = nodes[toId]
|
||||||
val to = nodes[toId]
|
if (!visited[toId] && to.types[type]) {
|
||||||
if (!visited[toId] && to.types[type]) {
|
visited.set(toId)
|
||||||
visited.set(toId)
|
prev[to] = from
|
||||||
prev[to] = from
|
front.add(to)
|
||||||
front.add(to)
|
if (to is Node.Source) {
|
||||||
if (to is Node.Source) {
|
source = to
|
||||||
source = to
|
endBfs = true
|
||||||
break@bfs
|
return@forEachEdge
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (endBfs) break@bfs
|
||||||
val reversedCastEdges = from.reversedCastEdges
|
val reversedCastEdges = from.reversedCastEdges
|
||||||
if (reversedCastEdges != null)
|
if (reversedCastEdges != null)
|
||||||
for (castEdge in reversedCastEdges) {
|
for (castEdge in reversedCastEdges) {
|
||||||
@@ -352,7 +348,7 @@ internal object Devirtualization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class CondensationBuilder {
|
private inner class CondensationBuilder(val directEdges: IntArray, val reversedEdges: IntArray) {
|
||||||
val nodes = constraintGraph.nodes
|
val nodes = constraintGraph.nodes
|
||||||
val nodesCount = nodes.size
|
val nodesCount = nodes.size
|
||||||
val visited = BitSet(nodesCount)
|
val visited = BitSet(nodesCount)
|
||||||
@@ -401,7 +397,7 @@ internal object Devirtualization {
|
|||||||
|
|
||||||
private fun findOrder(node: Node, order: IntArray) {
|
private fun findOrder(node: Node, order: IntArray) {
|
||||||
visited.set(node.id)
|
visited.set(node.id)
|
||||||
node.directEdges?.forEach {
|
directEdges.forEachEdge(node.id) {
|
||||||
if (!visited[it])
|
if (!visited[it])
|
||||||
findOrder(nodes[it], order)
|
findOrder(nodes[it], order)
|
||||||
}
|
}
|
||||||
@@ -411,7 +407,7 @@ internal object Devirtualization {
|
|||||||
private fun paint(node: Node) {
|
private fun paint(node: Node) {
|
||||||
visited.set(node.id)
|
visited.set(node.id)
|
||||||
multiNodes[index++] = node.id
|
multiNodes[index++] = node.id
|
||||||
node.reversedEdges?.forEach {
|
reversedEdges.forEachEdge(node.id) {
|
||||||
if (!visited[it])
|
if (!visited[it])
|
||||||
paint(nodes[it])
|
paint(nodes[it])
|
||||||
}
|
}
|
||||||
@@ -421,12 +417,11 @@ internal object Devirtualization {
|
|||||||
visited.set(multiNode.id)
|
visited.set(multiNode.id)
|
||||||
for (v in multiNode.multiNodeStart until multiNode.multiNodeEnd) {
|
for (v in multiNode.multiNodeStart until multiNode.multiNodeEnd) {
|
||||||
val node = nodes[multiNodes[v]]
|
val node = nodes[multiNodes[v]]
|
||||||
node.directEdges?.forEach {
|
directEdges.forEachEdge(node.id) {
|
||||||
val nextMultiNode = multiNodes[nodes[it].multiNodeStart]
|
val nextMultiNode = multiNodes[nodes[it].multiNodeStart]
|
||||||
if (!visited[nextMultiNode])
|
if (!visited[nextMultiNode])
|
||||||
findMultiNodesOrder(nodes[nextMultiNode], order)
|
findMultiNodesOrder(nodes[nextMultiNode], order)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
order[index++] = multiNode.id
|
order[index++] = multiNode.id
|
||||||
}
|
}
|
||||||
@@ -448,16 +443,14 @@ internal object Devirtualization {
|
|||||||
val rootSet = computeRootSet(context, moduleDFG, externalModulesDFG)
|
val rootSet = computeRootSet(context, moduleDFG, externalModulesDFG)
|
||||||
|
|
||||||
val nodesMap = mutableMapOf<DataFlowIR.Node, Node>()
|
val nodesMap = mutableMapOf<DataFlowIR.Node, Node>()
|
||||||
val constraintGraphBuilder =
|
|
||||||
ConstraintGraphBuilder(nodesMap, functions, typeHierarchy, rootSet, true)
|
val (instantiatingClasses, directEdges, reversedEdges) = buildConstraintGraph(nodesMap, functions, typeHierarchy, rootSet)
|
||||||
constraintGraphBuilder.build()
|
|
||||||
val instantiatingClasses = constraintGraphBuilder.instantiatingClasses
|
|
||||||
|
|
||||||
DEBUG_OUTPUT(0) {
|
DEBUG_OUTPUT(0) {
|
||||||
println("FULL CONSTRAINT GRAPH")
|
println("FULL CONSTRAINT GRAPH")
|
||||||
constraintGraph.nodes.forEach {
|
constraintGraph.nodes.forEach {
|
||||||
println(" NODE #${it.id}")
|
println(" NODE #${it.id}")
|
||||||
it.directEdges?.forEach {
|
directEdges.forEachEdge(it.id) {
|
||||||
println(" EDGE: #${it}z")
|
println(" EDGE: #${it}z")
|
||||||
}
|
}
|
||||||
it.directCastEdges?.forEach {
|
it.directCastEdges?.forEach {
|
||||||
@@ -472,17 +465,17 @@ internal object Devirtualization {
|
|||||||
|
|
||||||
constraintGraph.nodes.forEach {
|
constraintGraph.nodes.forEach {
|
||||||
if (it is Node.Source) {
|
if (it is Node.Source) {
|
||||||
assert(it.reversedEdges?.isEmpty() ?: true) { "A source node #${it.id} has incoming edges" }
|
assert(reversedEdges[it.id] == reversedEdges[it.id + 1]) { "A source node #${it.id} has incoming edges" }
|
||||||
assert(it.reversedCastEdges?.isEmpty() ?: true) { "A source node #${it.id} has incoming edges" }
|
assert(it.reversedCastEdges?.isEmpty() ?: true) { "A source node #${it.id} has incoming edges" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_OUTPUT(0) {
|
DEBUG_OUTPUT(0) {
|
||||||
println("CONSTRAINT GRAPH: ${constraintGraph.nodes.size} nodes, " +
|
println("CONSTRAINT GRAPH: ${constraintGraph.nodes.size} nodes, " +
|
||||||
"${constraintGraph.nodes.sumBy { (it.directEdges?.size ?: 0) + (it.directCastEdges?.size ?: 0) } } edges")
|
"${constraintGraph.nodes.sumBy { (directEdges[it.id + 1] - directEdges[it.id]) + (it.directCastEdges?.size ?: 0) } } edges")
|
||||||
}
|
}
|
||||||
|
|
||||||
val condensation = CondensationBuilder().build()
|
val condensation = CondensationBuilder(directEdges, reversedEdges).build()
|
||||||
val topologicalOrder = condensation.topologicalOrder.map { constraintGraph.nodes[it] }
|
val topologicalOrder = condensation.topologicalOrder.map { constraintGraph.nodes[it] }
|
||||||
|
|
||||||
DEBUG_OUTPUT(0) {
|
DEBUG_OUTPUT(0) {
|
||||||
@@ -519,7 +512,9 @@ internal object Devirtualization {
|
|||||||
continue // A source has no incoming edges.
|
continue // A source has no incoming edges.
|
||||||
val types = BitSet()
|
val types = BitSet()
|
||||||
condensation.forEachNode(multiNode) { node ->
|
condensation.forEachNode(multiNode) { node ->
|
||||||
node.reversedEdges?.forEach { types.or(constraintGraph.nodes[it].types) }
|
reversedEdges.forEachEdge(node.id) {
|
||||||
|
types.or(constraintGraph.nodes[it].types)
|
||||||
|
}
|
||||||
node.reversedCastEdges
|
node.reversedCastEdges
|
||||||
?.filter { it.node.priority < node.priority } // Doesn't contradict topological order.
|
?.filter { it.node.priority < node.priority } // Doesn't contradict topological order.
|
||||||
?.forEach {
|
?.forEach {
|
||||||
@@ -573,7 +568,7 @@ internal object Devirtualization {
|
|||||||
for (i in 0 until prevFrontSize) {
|
for (i in 0 until prevFrontSize) {
|
||||||
marked[prevFront[i]] = false
|
marked[prevFront[i]] = false
|
||||||
val node = constraintGraph.nodes[prevFront[i]]
|
val node = constraintGraph.nodes[prevFront[i]]
|
||||||
node.directEdges?.forEach { distNodeId ->
|
directEdges.forEachEdge(node.id) { distNodeId ->
|
||||||
val distNode = constraintGraph.nodes[distNodeId]
|
val distNode = constraintGraph.nodes[distNodeId]
|
||||||
if (marked[distNode.id])
|
if (marked[distNode.id])
|
||||||
distNode.types.or(node.types)
|
distNode.types.or(node.types)
|
||||||
@@ -632,7 +627,7 @@ internal object Devirtualization {
|
|||||||
(virtualCall.irCallSite?.let { ir2stringWhole(it) }
|
(virtualCall.irCallSite?.let { ir2stringWhole(it) }
|
||||||
?: virtualCall.callee.toString()))
|
?: virtualCall.callee.toString()))
|
||||||
println(" receiver is Virtual")
|
println(" receiver is Virtual")
|
||||||
printPathToType(receiverNode, VIRTUAL_TYPE_ID)
|
printPathToType(reversedEdges, receiverNode, VIRTUAL_TYPE_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@@ -652,7 +647,7 @@ internal object Devirtualization {
|
|||||||
|
|
||||||
DEBUG_OUTPUT(0) {
|
DEBUG_OUTPUT(0) {
|
||||||
println("Path to type $type")
|
println("Path to type $type")
|
||||||
printPathToType(receiverNode, it)
|
printPathToType(reversedEdges, receiverNode, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
possibleReceivers.add(type)
|
possibleReceivers.add(type)
|
||||||
@@ -703,6 +698,83 @@ internal object Devirtualization {
|
|||||||
return AnalysisResult(result.asSequence().associateBy({ it.key }, { it.value.first }), typeHierarchy)
|
return AnalysisResult(result.asSequence().associateBy({ it.key }, { it.value.first }), typeHierarchy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Both [directEdges] and [reversedEdges] are the array representation of a graph:
|
||||||
|
// for each node v the edges of that node are stored in edges[edges[v] until edges[v + 1]].
|
||||||
|
private data class ConstraintGraphBuildResult(val instantiatingClasses: BitSet,
|
||||||
|
val directEdges: IntArray, val reversedEdges: IntArray)
|
||||||
|
|
||||||
|
// Here we're dividing the build process onto two phases:
|
||||||
|
// 1. build bag of edges and direct edges array;
|
||||||
|
// 2. build reversed edges array from the direct edges array.
|
||||||
|
// This is to lower memory usage (all of these edges structures are more or less equal by size),
|
||||||
|
// and by that we're only holding references to two out of three of them.
|
||||||
|
private fun buildConstraintGraph(nodesMap: MutableMap<DataFlowIR.Node, Node>,
|
||||||
|
functions: Map<DataFlowIR.FunctionSymbol, DataFlowIR.Function>,
|
||||||
|
typeHierarchy: TypeHierarchy,
|
||||||
|
rootSet: List<DataFlowIR.FunctionSymbol>
|
||||||
|
): ConstraintGraphBuildResult {
|
||||||
|
val precursor = buildConstraintGraphPrecursor(nodesMap, functions, typeHierarchy, rootSet)
|
||||||
|
return ConstraintGraphBuildResult(precursor.instantiatingClasses, precursor.directEdges,
|
||||||
|
buildReversedEdges(precursor.directEdges, precursor.reversedEdgesCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConstraintGraphPrecursor(val instantiatingClasses: BitSet,
|
||||||
|
val directEdges: IntArray, val reversedEdgesCount: IntArrayList)
|
||||||
|
|
||||||
|
private fun buildReversedEdges(directEdges: IntArray, reversedEdgesCount: IntArrayList): IntArray {
|
||||||
|
val numberOfNodes = constraintGraph.nodes.size
|
||||||
|
var edgesArraySize = numberOfNodes + 1
|
||||||
|
for (v in 0 until numberOfNodes)
|
||||||
|
edgesArraySize += reversedEdgesCount[v]
|
||||||
|
val reversedEdges = IntArray(edgesArraySize)
|
||||||
|
var index = numberOfNodes + 1
|
||||||
|
for (v in 0..numberOfNodes) {
|
||||||
|
reversedEdges[v] = index
|
||||||
|
index += reversedEdgesCount[v]
|
||||||
|
reversedEdgesCount[v] = 0
|
||||||
|
}
|
||||||
|
for (from in 0 until numberOfNodes) {
|
||||||
|
directEdges.forEachEdge(from) { to ->
|
||||||
|
reversedEdges[reversedEdges[to] + (reversedEdgesCount[to]++)] = from
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reversedEdges
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildConstraintGraphPrecursor(nodesMap: MutableMap<DataFlowIR.Node, Node>,
|
||||||
|
functions: Map<DataFlowIR.FunctionSymbol, DataFlowIR.Function>,
|
||||||
|
typeHierarchy: TypeHierarchy,
|
||||||
|
rootSet: List<DataFlowIR.FunctionSymbol>
|
||||||
|
): ConstraintGraphPrecursor {
|
||||||
|
val constraintGraphBuilder = ConstraintGraphBuilder(nodesMap, functions, typeHierarchy, rootSet, true)
|
||||||
|
constraintGraphBuilder.build()
|
||||||
|
val bagOfEdges = constraintGraphBuilder.bagOfEdges
|
||||||
|
val directEdgesCount = constraintGraphBuilder.directEdgesCount
|
||||||
|
val reversedEdgesCount = constraintGraphBuilder.reversedEdgesCount
|
||||||
|
val numberOfNodes = constraintGraph.nodes.size
|
||||||
|
// numberOfNodes + 1 for convenience.
|
||||||
|
directEdgesCount.reserve(numberOfNodes + 1)
|
||||||
|
reversedEdgesCount.reserve(numberOfNodes + 1)
|
||||||
|
var edgesArraySize = numberOfNodes + 1
|
||||||
|
for (v in 0 until numberOfNodes)
|
||||||
|
edgesArraySize += directEdgesCount[v]
|
||||||
|
val directEdges = IntArray(edgesArraySize)
|
||||||
|
var index = numberOfNodes + 1
|
||||||
|
for (v in 0..numberOfNodes) {
|
||||||
|
directEdges[v] = index
|
||||||
|
index += directEdgesCount[v]
|
||||||
|
directEdgesCount[v] = 0
|
||||||
|
}
|
||||||
|
for (bucket in bagOfEdges)
|
||||||
|
if (bucket != null)
|
||||||
|
for (edge in bucket) {
|
||||||
|
val from = edge.toInt()
|
||||||
|
val to = (edge shr 32).toInt()
|
||||||
|
directEdges[directEdges[from] + (directEdgesCount[from]++)] = to
|
||||||
|
}
|
||||||
|
return ConstraintGraphPrecursor(constraintGraphBuilder.instantiatingClasses, directEdges, reversedEdgesCount)
|
||||||
|
}
|
||||||
|
|
||||||
private class ConstraintGraphVirtualCall(val caller: Function, val virtualCall: DataFlowIR.Node.VirtualCall,
|
private class ConstraintGraphVirtualCall(val caller: Function, val virtualCall: DataFlowIR.Node.VirtualCall,
|
||||||
val arguments: List<Node>, val returnsNode: Node)
|
val arguments: List<Node>, val returnsNode: Node)
|
||||||
|
|
||||||
@@ -720,6 +792,62 @@ internal object Devirtualization {
|
|||||||
private val virtualTypeFilter = BitSet().apply { set(VIRTUAL_TYPE_ID) }
|
private val virtualTypeFilter = BitSet().apply { set(VIRTUAL_TYPE_ID) }
|
||||||
val instantiatingClasses = BitSet()
|
val instantiatingClasses = BitSet()
|
||||||
|
|
||||||
|
private val preliminaryNumberOfNodes =
|
||||||
|
allTypes.size + // A possible source node for each type.
|
||||||
|
functions.size * 2 + // <returns> and <throws> nodes for each function.
|
||||||
|
functions.values.sumBy { it.body.nodes.size } + // A node for each DataFlowIR.Node.
|
||||||
|
functions.values
|
||||||
|
.sumBy { function ->
|
||||||
|
function.body.nodes.count { node ->
|
||||||
|
// A cast if types are different.
|
||||||
|
node is DataFlowIR.Node.Call
|
||||||
|
&& node.returnType.resolved() != node.callee.returnParameter.type.resolved()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPrime(x: Int): Boolean {
|
||||||
|
if (x <= 3) return true
|
||||||
|
if (x % 2 == 0) return false
|
||||||
|
var r = 3
|
||||||
|
while (r * r <= x) {
|
||||||
|
if (x % r == 0) return false
|
||||||
|
r += 2
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makePrime(x: Int): Int {
|
||||||
|
var x = x
|
||||||
|
while (true) {
|
||||||
|
if (isPrime(x)) return x
|
||||||
|
++x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A heuristic: the number of edges in the data flow graph
|
||||||
|
// for any reasonable program is linear in number of nodes.
|
||||||
|
val bagOfEdges = arrayOfNulls<LongArrayList>(makePrime(preliminaryNumberOfNodes * 5))
|
||||||
|
val directEdgesCount = IntArrayList()
|
||||||
|
val reversedEdgesCount = IntArrayList()
|
||||||
|
|
||||||
|
private fun addEdge(from: Node, to: Node) {
|
||||||
|
val fromId = from.id
|
||||||
|
val toId = to.id
|
||||||
|
val value = fromId.toLong() or (toId.toLong() shl 32)
|
||||||
|
// This is 64-bit extension of a hashing method from Knuth's "The Art of Computer Programming".
|
||||||
|
// The magic constant is the closest prime to 2^64 * phi, where phi is the golden ratio.
|
||||||
|
val bucketIdx = ((value.toULong() * 11400714819323198393UL) % bagOfEdges.size.toUInt()).toInt()
|
||||||
|
val bucket = bagOfEdges[bucketIdx] ?: LongArrayList().also { bagOfEdges[bucketIdx] = it }
|
||||||
|
for (x in bucket)
|
||||||
|
if (x == value) return
|
||||||
|
bucket.add(value)
|
||||||
|
|
||||||
|
directEdgesCount.reserve(fromId + 1)
|
||||||
|
directEdgesCount[fromId]++
|
||||||
|
reversedEdgesCount.reserve(toId + 1)
|
||||||
|
reversedEdgesCount[toId]++
|
||||||
|
}
|
||||||
|
|
||||||
private fun concreteType(type: DataFlowIR.Type.Declared): Int {
|
private fun concreteType(type: DataFlowIR.Type.Declared): Int {
|
||||||
assert(!(type.isAbstract && type.isFinal)) { "Incorrect type: $type" }
|
assert(!(type.isAbstract && type.isFinal)) { "Incorrect type: $type" }
|
||||||
return if (type.isAbstract)
|
return if (type.isAbstract)
|
||||||
@@ -749,9 +877,9 @@ internal object Devirtualization {
|
|||||||
val fieldType = field.type.resolved()
|
val fieldType = field.type.resolved()
|
||||||
// Some user of our library might place some value into the field.
|
// Some user of our library might place some value into the field.
|
||||||
if (fieldType.isFinal)
|
if (fieldType.isFinal)
|
||||||
concreteClass(fieldType).addEdge(fieldNode)
|
addEdge(concreteClass(fieldType), fieldNode)
|
||||||
else
|
else
|
||||||
constraintGraph.virtualNode.addEdge(fieldNode)
|
addEdge(constraintGraph.virtualNode, fieldNode)
|
||||||
}
|
}
|
||||||
fieldNode
|
fieldNode
|
||||||
}
|
}
|
||||||
@@ -777,9 +905,8 @@ internal object Devirtualization {
|
|||||||
} else {
|
} else {
|
||||||
// String arguments are implicitly put into the <args> array parameter of <main>.
|
// String arguments are implicitly put into the <args> array parameter of <main>.
|
||||||
addInstantiatingClass(symbolTable.mapType(context.irBuiltIns.stringType).resolved())
|
addInstantiatingClass(symbolTable.mapType(context.irBuiltIns.stringType).resolved())
|
||||||
concreteClass(symbolTable.mapType(context.irBuiltIns.stringType).resolved()).addEdge(
|
addEdge(concreteClass(symbolTable.mapType(context.irBuiltIns.stringType).resolved()),
|
||||||
fieldNode(constraintGraph.arrayItemField)
|
fieldNode(constraintGraph.arrayItemField))
|
||||||
)
|
|
||||||
// Conservatively assume each associated object could be instantiated.
|
// Conservatively assume each associated object could be instantiated.
|
||||||
context.irModule!!.acceptChildrenVoid(object: IrElementVisitorVoid {
|
context.irModule!!.acceptChildrenVoid(object: IrElementVisitorVoid {
|
||||||
override fun visitElement(element: IrElement) {
|
override fun visitElement(element: IrElement) {
|
||||||
@@ -803,8 +930,8 @@ internal object Devirtualization {
|
|||||||
val functionConstraintGraph = constraintGraph.functions[symbol]!!
|
val functionConstraintGraph = constraintGraph.functions[symbol]!!
|
||||||
|
|
||||||
body.nodes.forEach { dfgNodeToConstraintNode(functionConstraintGraph, it) }
|
body.nodes.forEach { dfgNodeToConstraintNode(functionConstraintGraph, it) }
|
||||||
functionNodesMap[body.returns]!!.addEdge(functionConstraintGraph.returns)
|
addEdge(functionNodesMap[body.returns]!!, functionConstraintGraph.returns)
|
||||||
functionNodesMap[body.throws]!!.addEdge(functionConstraintGraph.throws)
|
addEdge(functionNodesMap[body.throws]!!, functionConstraintGraph.throws)
|
||||||
|
|
||||||
DEBUG_OUTPUT(0) {
|
DEBUG_OUTPUT(0) {
|
||||||
println("CONSTRAINT GRAPH FOR $symbol")
|
println("CONSTRAINT GRAPH FOR $symbol")
|
||||||
@@ -830,10 +957,14 @@ internal object Devirtualization {
|
|||||||
for (list in typesVirtualCallSites)
|
for (list in typesVirtualCallSites)
|
||||||
for (virtualCall in list) {
|
for (virtualCall in list) {
|
||||||
val returnType = virtualCall.virtualCall.returnType.resolved()
|
val returnType = virtualCall.virtualCall.returnType.resolved()
|
||||||
if (returnType.isFinal && (virtualCall.returnsNode.reversedEdges?.size ?: 0) == 0) {
|
val totalIncomingEdgesToReturnsNode =
|
||||||
|
if (virtualCall.returnsNode.id >= reversedEdgesCount.size)
|
||||||
|
0
|
||||||
|
else reversedEdgesCount[virtualCall.returnsNode.id]
|
||||||
|
if (returnType.isFinal && totalIncomingEdgesToReturnsNode == 0) {
|
||||||
// If we are in a library and facing final return type with no possible callees -
|
// If we are in a library and facing final return type with no possible callees -
|
||||||
// this type still can be returned by some user of this library, so propagate it explicitly.
|
// this type still can be returned by some user of this library, so propagate it explicitly.
|
||||||
concreteClass(returnType).addEdge(virtualCall.returnsNode)
|
addEdge(concreteClass(returnType), virtualCall.returnsNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -852,7 +983,7 @@ internal object Devirtualization {
|
|||||||
constraintGraph.virtualNode // TODO: OBJC-INTEROP-GENERATED-CLASSES
|
constraintGraph.virtualNode // TODO: OBJC-INTEROP-GENERATED-CLASSES
|
||||||
else
|
else
|
||||||
concreteClass(resolvedType)
|
concreteClass(resolvedType)
|
||||||
node.addEdge(parameters[index])
|
addEdge(node, parameters[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,8 +1015,8 @@ internal object Devirtualization {
|
|||||||
|
|
||||||
val callee = receiverType.calleeAt(virtualCall.virtualCall)
|
val callee = receiverType.calleeAt(virtualCall.virtualCall)
|
||||||
|
|
||||||
doCall(virtualCall.caller, callee, virtualCall.arguments,
|
addEdge(doCall(virtualCall.caller, callee, virtualCall.arguments,
|
||||||
callee.returnParameter.type.resolved()).addEdge(virtualCall.returnsNode)
|
callee.returnParameter.type.resolved()), virtualCall.returnsNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkSupertypes(type: DataFlowIR.Type.Declared,
|
private fun checkSupertypes(type: DataFlowIR.Type.Declared,
|
||||||
@@ -953,7 +1084,7 @@ internal object Devirtualization {
|
|||||||
" provided ${arguments.size}"
|
" provided ${arguments.size}"
|
||||||
}
|
}
|
||||||
callee.parameters.forEachIndexed { index, parameter ->
|
callee.parameters.forEachIndexed { index, parameter ->
|
||||||
arguments[index].addEdge(parameter)
|
addEdge(arguments[index], parameter)
|
||||||
}
|
}
|
||||||
return castIfNeeded(caller, callee.returns, callee.symbol.returnParameter.type.resolved(), returnType)
|
return castIfNeeded(caller, callee.returns, callee.symbol.returnParameter.type.resolved(), returnType)
|
||||||
}
|
}
|
||||||
@@ -967,9 +1098,9 @@ internal object Devirtualization {
|
|||||||
val fictitiousReturnNode = ordinaryNode { "External$resolvedCallee" }
|
val fictitiousReturnNode = ordinaryNode { "External$resolvedCallee" }
|
||||||
if (returnType.isFinal) {
|
if (returnType.isFinal) {
|
||||||
addInstantiatingClass(returnType)
|
addInstantiatingClass(returnType)
|
||||||
concreteClass(returnType).addEdge(fictitiousReturnNode)
|
addEdge(concreteClass(returnType), fictitiousReturnNode)
|
||||||
} else {
|
} else {
|
||||||
constraintGraph.virtualNode.addEdge(fictitiousReturnNode)
|
addEdge(constraintGraph.virtualNode, fictitiousReturnNode)
|
||||||
// TODO: Unconservative way - when we can use it?
|
// TODO: Unconservative way - when we can use it?
|
||||||
// TODO: OBJC-INTEROP-GENERATED-CLASSES
|
// TODO: OBJC-INTEROP-GENERATED-CLASSES
|
||||||
// typeHierarchy.inheritorsOf(returnType)
|
// typeHierarchy.inheritorsOf(returnType)
|
||||||
@@ -980,7 +1111,7 @@ internal object Devirtualization {
|
|||||||
fictitiousReturnNode
|
fictitiousReturnNode
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
calleeConstraintGraph.throws.addEdge(caller.throws)
|
addEdge(calleeConstraintGraph.throws, caller.throws)
|
||||||
doCall(caller, calleeConstraintGraph, arguments, returnType)
|
doCall(caller, calleeConstraintGraph, arguments, returnType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1014,7 +1145,7 @@ internal object Devirtualization {
|
|||||||
value
|
value
|
||||||
else
|
else
|
||||||
doCast(function, value, actualType)
|
doCast(function, value, actualType)
|
||||||
castedValue.addEdge(fieldNode)
|
addEdge(castedValue, fieldNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node is DataFlowIR.Node.Variable && node.kind != DataFlowIR.VariableKind.Temporary) {
|
if (node is DataFlowIR.Node.Variable && node.kind != DataFlowIR.VariableKind.Temporary) {
|
||||||
@@ -1023,7 +1154,7 @@ internal object Devirtualization {
|
|||||||
variableNode = ordinaryNode { "Variable\$${function.symbol}" }
|
variableNode = ordinaryNode { "Variable\$${function.symbol}" }
|
||||||
variables[node] = variableNode
|
variables[node] = variableNode
|
||||||
for (value in node.values) {
|
for (value in node.values) {
|
||||||
edgeToConstraintNode(value).addEdge(variableNode)
|
addEdge(edgeToConstraintNode(value), variableNode)
|
||||||
}
|
}
|
||||||
if (node.kind == DataFlowIR.VariableKind.CatchParameter)
|
if (node.kind == DataFlowIR.VariableKind.CatchParameter)
|
||||||
function.throws.addCastEdge(createCastEdge(variableNode, node.type.resolved()))
|
function.throws.addCastEdge(createCastEdge(variableNode, node.type.resolved()))
|
||||||
@@ -1081,7 +1212,7 @@ internal object Devirtualization {
|
|||||||
val arguments = node.arguments.map(::edgeToConstraintNode)
|
val arguments = node.arguments.map(::edgeToConstraintNode)
|
||||||
val receiverNode = arguments[0]
|
val receiverNode = arguments[0]
|
||||||
if (receiverType == DataFlowIR.Type.Virtual)
|
if (receiverType == DataFlowIR.Type.Virtual)
|
||||||
constraintGraph.virtualNode.addEdge(receiverNode)
|
addEdge(constraintGraph.virtualNode, receiverNode)
|
||||||
|
|
||||||
if (entryPoint == null && returnType.isFinal) {
|
if (entryPoint == null && returnType.isFinal) {
|
||||||
// If we are in a library and facing final return type then
|
// If we are in a library and facing final return type then
|
||||||
@@ -1095,7 +1226,7 @@ internal object Devirtualization {
|
|||||||
ConstraintGraphVirtualCall(function, node, arguments, returnsNode))
|
ConstraintGraphVirtualCall(function, node, arguments, returnsNode))
|
||||||
forEachBitInBoth(typeHierarchy.inheritorsOf(receiverType), instantiatingClasses) {
|
forEachBitInBoth(typeHierarchy.inheritorsOf(receiverType), instantiatingClasses) {
|
||||||
val actualCallee = allTypes[it].calleeAt(node)
|
val actualCallee = allTypes[it].calleeAt(node)
|
||||||
doCall(actualCallee, arguments, actualCallee.returnParameter.type.resolved()).addEdge(returnsNode)
|
addEdge(doCall(actualCallee, arguments, actualCallee.returnParameter.type.resolved()), returnsNode)
|
||||||
}
|
}
|
||||||
// Add cast to [Virtual] edge from receiver to returns, if return type is not final.
|
// Add cast to [Virtual] edge from receiver to returns, if return type is not final.
|
||||||
// With this we're reflecting the fact that unknown function can return anything.
|
// With this we're reflecting the fact that unknown function can return anything.
|
||||||
@@ -1153,7 +1284,7 @@ internal object Devirtualization {
|
|||||||
is DataFlowIR.Node.Variable ->
|
is DataFlowIR.Node.Variable ->
|
||||||
node.values.map { edgeToConstraintNode(it) }.let { values ->
|
node.values.map { edgeToConstraintNode(it) }.let { values ->
|
||||||
ordinaryNode { "TempVar\$${function.symbol}" }.also { node ->
|
ordinaryNode { "TempVar\$${function.symbol}" }.also { node ->
|
||||||
values.forEach { it.addEdge(node) }
|
values.forEach { addEdge(it, node) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+47
@@ -54,6 +54,53 @@ class IntArrayList : Iterable<Int> {
|
|||||||
|
|
||||||
override fun hasNext() = index < size
|
override fun hasNext() = index < size
|
||||||
|
|
||||||
|
override fun next() = get(index++)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LongArrayList : Iterable<Long> {
|
||||||
|
private var array = LongArray(3)
|
||||||
|
private var length = 0
|
||||||
|
|
||||||
|
val size get() = length
|
||||||
|
|
||||||
|
fun isEmpty() = size == 0
|
||||||
|
|
||||||
|
fun add(x: Long) {
|
||||||
|
length++
|
||||||
|
ensureCapacity(length)
|
||||||
|
set(length - 1, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(index: Int): Long = when {
|
||||||
|
index < 0 || index >= length -> throw IndexOutOfBoundsException("Index out of range: $index")
|
||||||
|
|
||||||
|
else -> array[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set(index: Int, x: Long) = when {
|
||||||
|
index < 0 || index >= length -> throw IndexOutOfBoundsException("Index out of range: $index")
|
||||||
|
|
||||||
|
else -> array[index] = x
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun iterator(): Iterator<Long> = Itr()
|
||||||
|
|
||||||
|
private fun ensureCapacity(minCapacity: Int) {
|
||||||
|
val oldArray = array
|
||||||
|
if (minCapacity > oldArray.size) {
|
||||||
|
var newSize = oldArray.size * 3 / 2
|
||||||
|
if (minCapacity > newSize)
|
||||||
|
newSize = minCapacity
|
||||||
|
array = oldArray.copyOf(newSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Itr : Iterator<Long> {
|
||||||
|
private var index = 0
|
||||||
|
|
||||||
|
override fun hasNext() = index < size
|
||||||
|
|
||||||
override fun next() = get(index++)
|
override fun next() = get(index++)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user