Implemented devirtualization analysis
This commit is contained in:
+1
-1
@@ -65,7 +65,7 @@ internal class DirectedGraphCondensationBuilder<K, out N: DirectedGraphNode<K>>(
|
|||||||
findMultiNodesOrder(it)
|
findMultiNodesOrder(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
return DirectedGraphCondensation(multiNodesOrder)
|
return DirectedGraphCondensation(multiNodesOrder.reversed())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findOrder(node: N) {
|
private fun findOrder(node: N) {
|
||||||
|
|||||||
+1
@@ -64,6 +64,7 @@ enum class KonanPhase(val description: String,
|
|||||||
/* ... ... */ BUILD_DFG("Data flow graph building"),
|
/* ... ... */ BUILD_DFG("Data flow graph building"),
|
||||||
/* ... ... */ SERIALIZE_DFG("Data flow graph serializing", BUILD_DFG),
|
/* ... ... */ SERIALIZE_DFG("Data flow graph serializing", BUILD_DFG),
|
||||||
/* ... ... */ DESERIALIZE_DFG("Data flow graph deserializing"),
|
/* ... ... */ DESERIALIZE_DFG("Data flow graph deserializing"),
|
||||||
|
/* ... ... */ DEVIRTUALIZATION("Devirtualization", BUILD_DFG, DESERIALIZE_DFG),
|
||||||
/* ... ... */ ESCAPE_ANALYSIS("Escape analysis", BUILD_DFG, DESERIALIZE_DFG, enabled = false),
|
/* ... ... */ ESCAPE_ANALYSIS("Escape analysis", BUILD_DFG, DESERIALIZE_DFG, enabled = false),
|
||||||
/* ... ... */ CODEGEN("Code Generation"),
|
/* ... ... */ CODEGEN("Code Generation"),
|
||||||
/* ... ... */ BITCODE_LINKER("Bitcode linking"),
|
/* ... ... */ BITCODE_LINKER("Bitcode linking"),
|
||||||
|
|||||||
+7
-1
@@ -91,6 +91,12 @@ internal fun emitLLVM(context: Context) {
|
|||||||
|
|
||||||
val lifetimes = mutableMapOf<IrElement, Lifetime>()
|
val lifetimes = mutableMapOf<IrElement, Lifetime>()
|
||||||
val codegenVisitor = CodeGeneratorVisitor(context, lifetimes)
|
val codegenVisitor = CodeGeneratorVisitor(context, lifetimes)
|
||||||
|
@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
|
||||||
|
var devirtualizationAnalysisResult: Map<DataFlowIR.Node.VirtualCall, Devirtualization.DevirtualizedCallSite>? = null
|
||||||
|
phaser.phase(KonanPhase.DEVIRTUALIZATION) {
|
||||||
|
devirtualizationAnalysisResult = Devirtualization.analyze(context, moduleDFG!!, externalModulesDFG!!)
|
||||||
|
}
|
||||||
|
|
||||||
phaser.phase(KonanPhase.ESCAPE_ANALYSIS) {
|
phaser.phase(KonanPhase.ESCAPE_ANALYSIS) {
|
||||||
val callGraph = CallGraphBuilder(context, moduleDFG!!, externalModulesDFG!!).build()
|
val callGraph = CallGraphBuilder(context, moduleDFG!!, externalModulesDFG!!).build()
|
||||||
EscapeAnalysis.computeLifetimes(moduleDFG!!, externalModulesDFG!!, callGraph, lifetimes)
|
EscapeAnalysis.computeLifetimes(moduleDFG!!, externalModulesDFG!!, callGraph, lifetimes)
|
||||||
@@ -2509,4 +2515,4 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
|
|
||||||
internal data class LocationInfo(val scope:DIScopeOpaqueRef,
|
internal data class LocationInfo(val scope:DIScopeOpaqueRef,
|
||||||
val line:Int,
|
val line:Int,
|
||||||
val column:Int)
|
val column:Int)
|
||||||
+14
@@ -551,5 +551,19 @@ internal object DataFlowIR {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPrivateFunctionsTableForExport() =
|
||||||
|
functionMap
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.key is FunctionDescriptor }
|
||||||
|
.filter { it.value.let { it is DataFlowIR.FunctionSymbol.Declared && it.symbolTableIndex >= 0 } }
|
||||||
|
.sortedBy { (it.value as DataFlowIR.FunctionSymbol.Declared).symbolTableIndex }
|
||||||
|
.apply {
|
||||||
|
forEachIndexed { index, entry ->
|
||||||
|
assert((entry.value as DataFlowIR.FunctionSymbol.Declared).symbolTableIndex == index) { "Inconsistent function table" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map { it.key as FunctionDescriptor }
|
||||||
|
.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+852
@@ -0,0 +1,852 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.backend.konan.optimizations
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.ir.ir2stringWhole
|
||||||
|
import org.jetbrains.kotlin.backend.common.pop
|
||||||
|
import org.jetbrains.kotlin.backend.common.push
|
||||||
|
import org.jetbrains.kotlin.backend.konan.*
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
// TODO: Exceptions.
|
||||||
|
|
||||||
|
// Devirtualization analysis is performed using Variable Type Analysis algorithm.
|
||||||
|
// See http://web.cs.ucla.edu/~palsberg/tba/papers/sundaresan-et-al-oopsla00.pdf for details.
|
||||||
|
internal object Devirtualization {
|
||||||
|
|
||||||
|
private val DEBUG = 0
|
||||||
|
|
||||||
|
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
|
||||||
|
if (DEBUG > severity) block()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val TAKE_NAMES = false // Take fqNames for all functions and types (for debug purposes).
|
||||||
|
|
||||||
|
private inline fun takeName(block: () -> String) = if (TAKE_NAMES) block() else null
|
||||||
|
|
||||||
|
fun computeRootSet(context: Context, moduleDFG: ModuleDFG, externalModulesDFG: ExternalModulesDFG)
|
||||||
|
: List<DataFlowIR.FunctionSymbol> {
|
||||||
|
|
||||||
|
fun DataFlowIR.FunctionSymbol.resolved(): DataFlowIR.FunctionSymbol {
|
||||||
|
if (this is DataFlowIR.FunctionSymbol.External)
|
||||||
|
return externalModulesDFG.publicFunctions[this.hash] ?: this
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
val entryPoint = findMainEntryPoint(context)
|
||||||
|
val exportedFunctions =
|
||||||
|
if (entryPoint != null)
|
||||||
|
listOf(moduleDFG.symbolTable.mapFunction(entryPoint).resolved())
|
||||||
|
else
|
||||||
|
// In a library every public function and every function accessible via virtual call belongs to the rootset.
|
||||||
|
moduleDFG.functions.keys.filterIsInstance<DataFlowIR.FunctionSymbol.Public>() +
|
||||||
|
moduleDFG.symbolTable.classMap.values
|
||||||
|
.filterIsInstance<DataFlowIR.Type.Declared>()
|
||||||
|
.flatMap { it.vtable + it.itable.values }
|
||||||
|
.filterIsInstance<DataFlowIR.FunctionSymbol.Declared>()
|
||||||
|
.filter { moduleDFG.functions.containsKey(it) }
|
||||||
|
// TODO: Are globals inititalizers always called whether they are actually reachable from roots or not?
|
||||||
|
val globalInitializers =
|
||||||
|
moduleDFG.functions.keys.filter { it.isGlobalInitializer } +
|
||||||
|
externalModulesDFG.functionDFGs.keys.filter { it.isGlobalInitializer }
|
||||||
|
|
||||||
|
return (exportedFunctions + globalInitializers).distinct()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BitSet.format(allTypes: List<DataFlowIR.Type.Declared>): String {
|
||||||
|
return allTypes.withIndex().filter { this[it.index] }.joinToString { it.value.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val VIRTUAL_TYPE_ID = 0 // Id of [DataFlowIR.Type.Virtual].
|
||||||
|
|
||||||
|
private class DevirtualizationAnalysis(val context: Context,
|
||||||
|
val moduleDFG: ModuleDFG,
|
||||||
|
val externalModulesDFG: ExternalModulesDFG) {
|
||||||
|
|
||||||
|
private val entryPoint = findMainEntryPoint(context)
|
||||||
|
|
||||||
|
private val symbolTable = moduleDFG.symbolTable
|
||||||
|
|
||||||
|
sealed class Node(val id: Int) : DirectedGraphNode<Node> {
|
||||||
|
override val directEdges = mutableListOf<Node>()
|
||||||
|
override val reversedEdges = mutableListOf<Node>()
|
||||||
|
override val key get() = this
|
||||||
|
|
||||||
|
val directCastEdges = mutableListOf<CastEdge>()
|
||||||
|
val reversedCastEdges = mutableListOf<CastEdge>()
|
||||||
|
|
||||||
|
val types = BitSet()
|
||||||
|
|
||||||
|
var priority = -1
|
||||||
|
|
||||||
|
fun addEdge(node: Node) {
|
||||||
|
directEdges += node
|
||||||
|
node.reversedEdges += this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addCastEdge(edge: CastEdge) {
|
||||||
|
directCastEdges += edge
|
||||||
|
edge.node.reversedCastEdges += CastEdge(this, edge.suitableTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun toString(allTypes: List<DataFlowIR.Type.Declared>): String
|
||||||
|
|
||||||
|
class Source(id: Int, typeId: Int, nameBuilder: () -> String): Node(id) {
|
||||||
|
val name = takeName(nameBuilder)
|
||||||
|
|
||||||
|
init {
|
||||||
|
types.set(typeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(allTypes: List<DataFlowIR.Type.Declared>): String {
|
||||||
|
return "Source(name='$name', types='${types.format(allTypes)}')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ordinary(id: Int, nameBuilder: () -> String) : Node(id) {
|
||||||
|
val name = takeName(nameBuilder)
|
||||||
|
|
||||||
|
override fun toString(allTypes: List<DataFlowIR.Type.Declared>): String {
|
||||||
|
return "Ordinary(name='$name', types='${types.format(allTypes)}')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CastEdge(val node: Node, val suitableTypes: BitSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Function(val symbol: DataFlowIR.FunctionSymbol, val parameters: Array<Node>, val returns: Node)
|
||||||
|
|
||||||
|
class VirtualCallSiteReceivers(val receiver: Node, val caller: DataFlowIR.FunctionSymbol, val devirtualizedCallees: List<DevirtualizedCallee>)
|
||||||
|
|
||||||
|
class ConstraintGraph : DirectedGraph<Node, Node> {
|
||||||
|
|
||||||
|
private var nodesCount = 0
|
||||||
|
|
||||||
|
override val nodes = mutableListOf<Node>()
|
||||||
|
override fun get(key: Node) = key
|
||||||
|
|
||||||
|
val voidNode = addNode { Node.Ordinary(it, { "Void" }) }
|
||||||
|
val virtualNode = addNode { Node.Source(it, VIRTUAL_TYPE_ID, { "Virtual" }) }
|
||||||
|
val arrayItemField = DataFlowIR.Field(null, 1, "Array\$Item")
|
||||||
|
val functions = mutableMapOf<DataFlowIR.FunctionSymbol, Function>()
|
||||||
|
val concreteClasses = mutableMapOf<DataFlowIR.Type.Declared, Node>()
|
||||||
|
val externalFunctions = mutableMapOf<DataFlowIR.FunctionSymbol, Node>()
|
||||||
|
val fields = mutableMapOf<DataFlowIR.Field, Node>() // Do not distinguish receivers.
|
||||||
|
val virtualCallSiteReceivers = mutableMapOf<DataFlowIR.Node.VirtualCall, VirtualCallSiteReceivers>()
|
||||||
|
|
||||||
|
private fun nextId(): Int = nodesCount++
|
||||||
|
|
||||||
|
fun addNode(nodeBuilder: (Int) -> Node) = nodeBuilder(nextId()).also { nodes.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val constraintGraph = ConstraintGraph()
|
||||||
|
|
||||||
|
private fun DataFlowIR.Type.resolved(): DataFlowIR.Type.Declared {
|
||||||
|
if (this is DataFlowIR.Type.Declared) return this
|
||||||
|
val hash = (this as DataFlowIR.Type.External).hash
|
||||||
|
return externalModulesDFG.publicTypes[hash] ?: error("Unable to resolve exported type $hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataFlowIR.FunctionSymbol.resolved(): DataFlowIR.FunctionSymbol {
|
||||||
|
if (this is DataFlowIR.FunctionSymbol.External)
|
||||||
|
return externalModulesDFG.publicFunctions[this.hash] ?: this
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class TypeHierarchy(types: List<DataFlowIR.Type.Declared>) {
|
||||||
|
private val typesSubTypes = mutableMapOf<DataFlowIR.Type.Declared, MutableList<DataFlowIR.Type.Declared>>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val visited = mutableSetOf<DataFlowIR.Type.Declared>()
|
||||||
|
|
||||||
|
fun processType(type: DataFlowIR.Type.Declared) {
|
||||||
|
if (type == DataFlowIR.Type.Virtual) return
|
||||||
|
if (!visited.add(type)) return
|
||||||
|
type.superTypes
|
||||||
|
.map { it.resolved() }
|
||||||
|
.forEach { superType ->
|
||||||
|
val subTypes = typesSubTypes.getOrPut(superType, { mutableListOf() })
|
||||||
|
subTypes += type
|
||||||
|
processType(superType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
types.forEach { processType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findAllInheritors(type: DataFlowIR.Type.Declared, result: MutableSet<DataFlowIR.Type.Declared>) {
|
||||||
|
if (!result.add(type)) return
|
||||||
|
typesSubTypes[type]?.forEach { findAllInheritors(it, result) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun inheritorsOf(type: DataFlowIR.Type.Declared): List<DataFlowIR.Type.Declared> {
|
||||||
|
val result = mutableSetOf<DataFlowIR.Type.Declared>()
|
||||||
|
findAllInheritors(type, result)
|
||||||
|
return result.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class InstantiationsSearcher(val functions: Map<DataFlowIR.FunctionSymbol, DataFlowIR.Function>,
|
||||||
|
val rootSet: List<DataFlowIR.FunctionSymbol>,
|
||||||
|
val typeHierarchy: TypeHierarchy) {
|
||||||
|
private val visited = mutableSetOf<DataFlowIR.FunctionSymbol>()
|
||||||
|
private val typesVirtualCallSites = mutableMapOf<DataFlowIR.Type.Declared, MutableList<DataFlowIR.Node.VirtualCall>>()
|
||||||
|
private val instantiatingClasses = mutableSetOf<DataFlowIR.Type.Declared>()
|
||||||
|
|
||||||
|
fun search(): Set<DataFlowIR.Type.Declared> {
|
||||||
|
// Rapid Type Analysis: find all instantiations and conservatively estimate call graph.
|
||||||
|
// Add all final parameters of the roots.
|
||||||
|
rootSet.map { functions[it]!! }
|
||||||
|
.forEach {
|
||||||
|
it.parameterTypes
|
||||||
|
.map { it.resolved() }
|
||||||
|
.filter { it.isFinal }
|
||||||
|
.forEach { addInstantiatingClass(it) }
|
||||||
|
}
|
||||||
|
if (entryPoint == null) {
|
||||||
|
// For library assume all public non-abstract classes could be instantiated.
|
||||||
|
moduleDFG.symbolTable.classMap.values
|
||||||
|
.asSequence()
|
||||||
|
.filterIsInstance<DataFlowIR.Type.Public>()
|
||||||
|
.filter { !it.isAbstract }
|
||||||
|
.forEach { addInstantiatingClass(it) }
|
||||||
|
}
|
||||||
|
// Traverse call graph from the roots.
|
||||||
|
rootSet.forEach { dfs(it) }
|
||||||
|
return instantiatingClasses
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addInstantiatingClass(type: DataFlowIR.Type) {
|
||||||
|
val resolvedType = type.resolved()
|
||||||
|
if (!instantiatingClasses.add(resolvedType)) return
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(1) { println("Adding instantiating class: $resolvedType") }
|
||||||
|
|
||||||
|
checkSupertypes(resolvedType, resolvedType, mutableSetOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processVirtualCall(virtualCall: DataFlowIR.Node.VirtualCall,
|
||||||
|
receiverType: DataFlowIR.Type.Declared) {
|
||||||
|
DEBUG_OUTPUT(1) {
|
||||||
|
println("Processing virtual call: ${virtualCall.callee}")
|
||||||
|
println("Receiver type: $receiverType")
|
||||||
|
}
|
||||||
|
|
||||||
|
val callee = when (virtualCall) {
|
||||||
|
is DataFlowIR.Node.VtableCall ->
|
||||||
|
receiverType.vtable[virtualCall.calleeVtableIndex]
|
||||||
|
|
||||||
|
is DataFlowIR.Node.ItableCall ->
|
||||||
|
receiverType.itable[virtualCall.calleeHash]!!
|
||||||
|
|
||||||
|
else -> error("Unreachable")
|
||||||
|
}
|
||||||
|
dfs(callee)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkSupertypes(type: DataFlowIR.Type.Declared,
|
||||||
|
inheritor: DataFlowIR.Type.Declared,
|
||||||
|
seenTypes: MutableSet<DataFlowIR.Type.Declared>) {
|
||||||
|
seenTypes += type
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(1) {
|
||||||
|
println("Checking supertype $type of $inheritor")
|
||||||
|
typesVirtualCallSites[type].let {
|
||||||
|
if (it == null)
|
||||||
|
println("None virtual call sites encountered yet")
|
||||||
|
else {
|
||||||
|
println("Virtual call sites:")
|
||||||
|
it.forEach {
|
||||||
|
println(" ${it.callee}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typesVirtualCallSites[type]?.let { virtualCallSites ->
|
||||||
|
var index = 0
|
||||||
|
while (index < virtualCallSites.size) {
|
||||||
|
processVirtualCall(virtualCallSites[index], inheritor)
|
||||||
|
++index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type.superTypes
|
||||||
|
.map { it.resolved() }
|
||||||
|
.filterNot { seenTypes.contains(it) }
|
||||||
|
.forEach { checkSupertypes(it, inheritor, seenTypes) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dfs(symbol: DataFlowIR.FunctionSymbol) {
|
||||||
|
val resolvedFunctionSymbol = symbol.resolved()
|
||||||
|
if (resolvedFunctionSymbol is DataFlowIR.FunctionSymbol.External) {
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(1) { println("Function $resolvedFunctionSymbol is external") }
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!visited.add(resolvedFunctionSymbol)) return
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(1) { println("Visiting $resolvedFunctionSymbol") }
|
||||||
|
|
||||||
|
val function = (moduleDFG.functions[resolvedFunctionSymbol] ?: externalModulesDFG.functionDFGs[resolvedFunctionSymbol])!!
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(1) { function.debugOutput() }
|
||||||
|
|
||||||
|
nodeLoop@for (node in function.body.nodes) {
|
||||||
|
when (node) {
|
||||||
|
is DataFlowIR.Node.NewObject -> {
|
||||||
|
addInstantiatingClass(node.returnType)
|
||||||
|
dfs(node.callee)
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataFlowIR.Node.Singleton -> {
|
||||||
|
addInstantiatingClass(node.type)
|
||||||
|
node.constructor?.let { dfs(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataFlowIR.Node.Const -> addInstantiatingClass(node.type)
|
||||||
|
|
||||||
|
is DataFlowIR.Node.StaticCall -> dfs(node.callee)
|
||||||
|
|
||||||
|
is DataFlowIR.Node.VirtualCall -> {
|
||||||
|
if (node.receiverType == DataFlowIR.Type.Virtual)
|
||||||
|
continue@nodeLoop
|
||||||
|
val receiverType = node.receiverType.resolved()
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(1) {
|
||||||
|
println("Adding virtual callsite:")
|
||||||
|
println(" Receiver: $receiverType")
|
||||||
|
println(" Callee: ${node.callee}")
|
||||||
|
println(" Inheritors:")
|
||||||
|
typeHierarchy.inheritorsOf(receiverType).forEach { println(" $it") }
|
||||||
|
println(" Encountered so far:")
|
||||||
|
typeHierarchy.inheritorsOf(receiverType)
|
||||||
|
.filter { instantiatingClasses.contains(it) }
|
||||||
|
.forEach { println(" $it") }
|
||||||
|
}
|
||||||
|
|
||||||
|
typesVirtualCallSites.getOrPut(receiverType, { mutableListOf() }).add(node)
|
||||||
|
typeHierarchy.inheritorsOf(receiverType)
|
||||||
|
.filter { instantiatingClasses.contains(it) }
|
||||||
|
.forEach { processVirtualCall(node, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BitSet.copy() = BitSet(this.size()).apply { this.or(this@copy) }
|
||||||
|
|
||||||
|
fun analyze(): Map<DataFlowIR.Node.VirtualCall, DevirtualizedCallSite> {
|
||||||
|
val functions = moduleDFG.functions + externalModulesDFG.functionDFGs
|
||||||
|
val typeHierarchy = TypeHierarchy(symbolTable.classMap.values.filterIsInstance<DataFlowIR.Type.Declared>() +
|
||||||
|
externalModulesDFG.allTypes)
|
||||||
|
val rootSet = computeRootSet(context, moduleDFG, externalModulesDFG)
|
||||||
|
|
||||||
|
val instantiatingClasses =
|
||||||
|
InstantiationsSearcher(functions, rootSet, typeHierarchy).search()
|
||||||
|
.withIndex()
|
||||||
|
.associate { it.value to (it.index + 1 /* 0 is reserved for [DataFlowIR.Type.Virtual] */) }
|
||||||
|
val allTypes = listOf(DataFlowIR.Type.Virtual) + instantiatingClasses.asSequence().sortedBy { it.value }.map { it.key }
|
||||||
|
|
||||||
|
val nodesMap = mutableMapOf<DataFlowIR.Node, Node>()
|
||||||
|
val constraintGraphBuilder = ConstraintGraphBuilder(nodesMap, functions, typeHierarchy, instantiatingClasses, allTypes, rootSet)
|
||||||
|
constraintGraphBuilder.build()
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("FULL CONSTRAINT GRAPH")
|
||||||
|
constraintGraph.nodes.forEach {
|
||||||
|
println(" NODE #${it.id}")
|
||||||
|
it.directEdges.forEach {
|
||||||
|
println(" EDGE: #${it.id}z")
|
||||||
|
}
|
||||||
|
it.directCastEdges.forEach {
|
||||||
|
println(" CAST EDGE: #${it.node.id}z casted to ${it.suitableTypes.format(allTypes)}")
|
||||||
|
}
|
||||||
|
allTypes.forEachIndexed { index, type ->
|
||||||
|
if (it.types[index])
|
||||||
|
println(" TYPE: $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constraintGraph.nodes.forEach {
|
||||||
|
if (it is Node.Source) {
|
||||||
|
assert(it.reversedEdges.isEmpty(), { "A source node #${it.id} has incoming edges" })
|
||||||
|
assert(it.reversedCastEdges.isEmpty(), { "A source node #${it.id} has incoming edges" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("CONSTRAINT GRAPH: ${constraintGraph.nodes.size} nodes, " +
|
||||||
|
"${constraintGraph.nodes.sumBy { it.directEdges.size + it.directCastEdges.size } } edges")
|
||||||
|
}
|
||||||
|
|
||||||
|
val topologicalOrder = DirectedGraphCondensationBuilder(constraintGraph).build().topologicalOrder
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("CONDENSATION")
|
||||||
|
topologicalOrder.forEachIndexed { index, multiNode ->
|
||||||
|
println(" MULTI-NODE #$index")
|
||||||
|
multiNode.nodes.forEach {
|
||||||
|
println(" #${it.id}: ${it.toString(allTypes)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topologicalOrder.forEachIndexed { index, multiNode -> multiNode.nodes.forEach { it.priority = index } }
|
||||||
|
|
||||||
|
// Handle all 'right-directed' edges.
|
||||||
|
// TODO: this is pessimistic handling of [DataFlowIR.Type.Virtual], think how to do it better.
|
||||||
|
for (multiNode in topologicalOrder) {
|
||||||
|
if (multiNode.nodes.size == 1 && multiNode.nodes.first() is Node.Source)
|
||||||
|
continue // A source has no incoming edges.
|
||||||
|
val types = BitSet()
|
||||||
|
for (node in multiNode.nodes) {
|
||||||
|
node.reversedEdges.forEach { types.or(it.types) }
|
||||||
|
node.reversedCastEdges
|
||||||
|
.filter { it.node.priority < node.priority } // Doesn't contradict topological order.
|
||||||
|
.forEach {
|
||||||
|
val sourceTypes = it.node.types.copy()
|
||||||
|
sourceTypes.and(it.suitableTypes)
|
||||||
|
types.or(sourceTypes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (node in multiNode.nodes)
|
||||||
|
node.types.or(types)
|
||||||
|
}
|
||||||
|
val badEdges = mutableListOf<Pair<Node, Node.CastEdge>>()
|
||||||
|
for (node in constraintGraph.nodes) {
|
||||||
|
node.directCastEdges
|
||||||
|
.filter { it.node.priority < node.priority } // Contradicts topological order.
|
||||||
|
.forEach { badEdges += node to it }
|
||||||
|
}
|
||||||
|
badEdges.sortBy { it.second.node.priority } // Heuristic.
|
||||||
|
|
||||||
|
do {
|
||||||
|
fun propagateTypes(node: Node, types: BitSet) {
|
||||||
|
node.types.or(types)
|
||||||
|
for (edge in node.directEdges) {
|
||||||
|
val missingTypes = types.copy().apply { andNot(edge.types) }
|
||||||
|
if (!missingTypes.isEmpty)
|
||||||
|
propagateTypes(edge, missingTypes)
|
||||||
|
}
|
||||||
|
for (castEdge in node.directCastEdges) {
|
||||||
|
val missingTypes = types.copy().apply { andNot(castEdge.node.types) }
|
||||||
|
missingTypes.and(castEdge.suitableTypes)
|
||||||
|
if (!missingTypes.isEmpty)
|
||||||
|
propagateTypes(castEdge.node, missingTypes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var end = true
|
||||||
|
for ((sourceNode, edge) in badEdges) {
|
||||||
|
val distNode = edge.node
|
||||||
|
val missingTypes = sourceNode.types.copy().apply { andNot(distNode.types) }
|
||||||
|
missingTypes.and(edge.suitableTypes)
|
||||||
|
if (!missingTypes.isEmpty) {
|
||||||
|
end = false
|
||||||
|
propagateTypes(distNode, missingTypes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (!end)
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
topologicalOrder.forEachIndexed { index, multiNode ->
|
||||||
|
println("Types of multi-node #$index")
|
||||||
|
for (node in multiNode.nodes) {
|
||||||
|
println(" Node #${node.id}")
|
||||||
|
allTypes.withIndex()
|
||||||
|
.filter { node.types[it.index] }
|
||||||
|
.forEach { println(" ${it.value}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mutableMapOf<DataFlowIR.Node.VirtualCall, Pair<DevirtualizedCallSite, DataFlowIR.FunctionSymbol>>()
|
||||||
|
val nothing = symbolTable.mapClass(context.builtIns.nothing)
|
||||||
|
functions.values
|
||||||
|
.asSequence()
|
||||||
|
.filter { constraintGraph.functions.containsKey(it.symbol) }
|
||||||
|
.flatMap { it.body.nodes.asSequence() }
|
||||||
|
.filterIsInstance<DataFlowIR.Node.VirtualCall>()
|
||||||
|
.forEach { virtualCall ->
|
||||||
|
assert (nodesMap[virtualCall] != null, { "Node for virtual call $virtualCall has not been built" })
|
||||||
|
val virtualCallSiteReceivers = constraintGraph.virtualCallSiteReceivers[virtualCall]
|
||||||
|
if (virtualCallSiteReceivers == null || virtualCallSiteReceivers.receiver.types[VIRTUAL_TYPE_ID]) {
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("Unable to devirtualize callsite ${virtualCall.callSite?.let { ir2stringWhole(it) } ?: virtualCall.toString() }")
|
||||||
|
println(" receiver is Virtual")
|
||||||
|
}
|
||||||
|
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val possibleReceivers = allTypes.withIndex()
|
||||||
|
.filter { virtualCallSiteReceivers.receiver.types[it.index] }
|
||||||
|
.map { it.value }
|
||||||
|
.filter { it != nothing }
|
||||||
|
val map = virtualCallSiteReceivers.devirtualizedCallees.associateBy({ it.receiverType }, { it })
|
||||||
|
result[virtualCall] = DevirtualizedCallSite(possibleReceivers.map { receiverType ->
|
||||||
|
assert (map[receiverType] != null) {
|
||||||
|
"Non-expected receiver type $receiverType at call site: " +
|
||||||
|
(virtualCall.callSite?.let { ir2stringWhole(it) } ?: virtualCall.toString())
|
||||||
|
}
|
||||||
|
val devirtualizedCallee = map[receiverType]!!
|
||||||
|
val callee = devirtualizedCallee.callee
|
||||||
|
if (callee is DataFlowIR.FunctionSymbol.Declared && callee.symbolTableIndex < 0)
|
||||||
|
error("Function ${devirtualizedCallee.receiverType}.$callee cannot be called virtually," +
|
||||||
|
" but actually is at call site: ${virtualCall.callSite?.let { ir2stringWhole(it) } ?: virtualCall.toString() }")
|
||||||
|
devirtualizedCallee
|
||||||
|
}) to virtualCallSiteReceivers.caller
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("Devirtualized from current module:")
|
||||||
|
result.forEach { virtualCall, devirtualizedCallSite ->
|
||||||
|
if (virtualCall.callSite != null) {
|
||||||
|
println("DEVIRTUALIZED")
|
||||||
|
println("FUNCTION: ${devirtualizedCallSite.second}")
|
||||||
|
println("CALL SITE: ${virtualCall.callSite?.let { ir2stringWhole(it) } ?: virtualCall.toString()}")
|
||||||
|
println("POSSIBLE RECEIVERS:")
|
||||||
|
devirtualizedCallSite.first.possibleCallees.forEach { println(" TYPE: ${it.receiverType}") }
|
||||||
|
devirtualizedCallSite.first.possibleCallees.forEach { println(" FUN: ${it.callee}") }
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("Devirtualized from external modules:")
|
||||||
|
result.forEach { virtualCall, devirtualizedCallSite ->
|
||||||
|
if (virtualCall.callSite == null) {
|
||||||
|
println("DEVIRTUALIZED")
|
||||||
|
println("FUNCTION: ${devirtualizedCallSite.second}")
|
||||||
|
println("CALL SITE: ${virtualCall.callSite?.let { ir2stringWhole(it) } ?: virtualCall.toString()}")
|
||||||
|
println("POSSIBLE RECEIVERS:")
|
||||||
|
devirtualizedCallSite.first.possibleCallees.forEach { println(" TYPE: ${it.receiverType}") }
|
||||||
|
devirtualizedCallSite.first.possibleCallees.forEach { println(" FUN: ${it.callee}") }
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.asSequence().associateBy({ it.key }, { it.value.first })
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class ConstraintGraphBuilder(val functionNodesMap: MutableMap<DataFlowIR.Node, Node>,
|
||||||
|
val functions: Map<DataFlowIR.FunctionSymbol, DataFlowIR.Function>,
|
||||||
|
val typeHierarchy: TypeHierarchy,
|
||||||
|
val instantiatingClasses: Map<DataFlowIR.Type.Declared, Int>,
|
||||||
|
val allTypes: List<DataFlowIR.Type.Declared>,
|
||||||
|
val rootSet: List<DataFlowIR.FunctionSymbol>) {
|
||||||
|
|
||||||
|
private val variables = mutableMapOf<DataFlowIR.Node.Variable, Node>()
|
||||||
|
|
||||||
|
private fun concreteType(type: DataFlowIR.Type.Declared): Int {
|
||||||
|
assert(!(type.isAbstract && type.isFinal)) { "Incorrect type: $type" }
|
||||||
|
return if (type.isAbstract) VIRTUAL_TYPE_ID else { instantiatingClasses[type] ?: error("Type $type is not instantiated") }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ordinaryNode(nameBuilder: () -> String) =
|
||||||
|
constraintGraph.addNode { Node.Ordinary(it, nameBuilder) }
|
||||||
|
|
||||||
|
private fun sourceNode(typeId: Int, nameBuilder: () -> String) =
|
||||||
|
constraintGraph.addNode { Node.Source(it, typeId, nameBuilder) }
|
||||||
|
|
||||||
|
private fun concreteClass(type: DataFlowIR.Type.Declared) =
|
||||||
|
constraintGraph.concreteClasses.getOrPut(type) { sourceNode(concreteType(type)) { "Class\$$type" } }
|
||||||
|
|
||||||
|
private fun fieldNode(field: DataFlowIR.Field) =
|
||||||
|
constraintGraph.fields.getOrPut(field) { ordinaryNode { "Field\$$field" } }
|
||||||
|
|
||||||
|
private var stack = mutableListOf<DataFlowIR.FunctionSymbol>()
|
||||||
|
|
||||||
|
fun build() {
|
||||||
|
rootSet.forEach { createFunctionConstraintGraph(it, true)!! }
|
||||||
|
while (stack.isNotEmpty()) {
|
||||||
|
val symbol = stack.pop()
|
||||||
|
val function = functions[symbol] ?: error("Unknown function: $symbol")
|
||||||
|
val body = function.body
|
||||||
|
val functionConstraintGraph = constraintGraph.functions[symbol]!!
|
||||||
|
|
||||||
|
body.nodes.forEach { dfgNodeToConstraintNode(functionConstraintGraph, it) }
|
||||||
|
functionNodesMap[body.returns]!!.addEdge(functionConstraintGraph.returns)
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("CONSTRAINT GRAPH FOR $symbol")
|
||||||
|
val ids = function.body.nodes.withIndex().associateBy({ it.value }, { it.index })
|
||||||
|
for (node in function.body.nodes) {
|
||||||
|
println("FT NODE #${ids[node]}")
|
||||||
|
DataFlowIR.Function.printNode(node, ids)
|
||||||
|
val constraintNode = functionNodesMap[node] ?: variables[node] ?: break
|
||||||
|
println(" CG NODE #${constraintNode.id}: ${constraintNode.toString(allTypes)}")
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
println("Returns: #${ids[function.body.returns]}")
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFunctionConstraintGraph(symbol: DataFlowIR.FunctionSymbol, isRoot: Boolean): Function? {
|
||||||
|
if (symbol is DataFlowIR.FunctionSymbol.External) return null
|
||||||
|
constraintGraph.functions[symbol]?.let { return it }
|
||||||
|
|
||||||
|
val function = functions[symbol] ?: error("Unknown function: $symbol")
|
||||||
|
val parameters = Array(symbol.numberOfParameters) { ordinaryNode { "Param#$it\$$symbol" } }
|
||||||
|
if (isRoot) {
|
||||||
|
// Exported function from the current module.
|
||||||
|
function.parameterTypes.forEachIndexed { index, type ->
|
||||||
|
val resolvedType = type.resolved()
|
||||||
|
val node = if (!resolvedType.isFinal)
|
||||||
|
constraintGraph.virtualNode
|
||||||
|
else
|
||||||
|
concreteClass(resolvedType)
|
||||||
|
node.addEdge(parameters[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val returnsNode = ordinaryNode { "Returns\$$symbol" }
|
||||||
|
val functionConstraintGraph = Function(symbol, parameters, returnsNode)
|
||||||
|
constraintGraph.functions[symbol] = functionConstraintGraph
|
||||||
|
|
||||||
|
stack.push(symbol)
|
||||||
|
|
||||||
|
return functionConstraintGraph
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCastEdge(node: Node, type: DataFlowIR.Type.Declared): Node.CastEdge {
|
||||||
|
val suitableTypes = BitSet()
|
||||||
|
suitableTypes.set(VIRTUAL_TYPE_ID)
|
||||||
|
for (inheritor in typeHierarchy.inheritorsOf(type)) {
|
||||||
|
instantiatingClasses[inheritor]?.let { suitableTypes.set(it) }
|
||||||
|
}
|
||||||
|
return Node.CastEdge(node, suitableTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun edgeToConstraintNode(function: Function,
|
||||||
|
edge: DataFlowIR.Edge): Node {
|
||||||
|
val result = dfgNodeToConstraintNode(function, edge.node)
|
||||||
|
val castToType = edge.castToType?.resolved() ?: return result
|
||||||
|
val castNode = ordinaryNode { "Cast\$${function.symbol}" }
|
||||||
|
val castEdge = createCastEdge(castNode, castToType)
|
||||||
|
result.addCastEdge(castEdge)
|
||||||
|
return castNode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a function DFG's node and creates a constraint graph node corresponding to it.
|
||||||
|
* Also creates all necessary edges.
|
||||||
|
*/
|
||||||
|
private fun dfgNodeToConstraintNode(function: Function, node: DataFlowIR.Node): Node {
|
||||||
|
|
||||||
|
fun edgeToConstraintNode(edge: DataFlowIR.Edge): Node =
|
||||||
|
edgeToConstraintNode(function, edge)
|
||||||
|
|
||||||
|
fun argumentToConstraintNode(argument: Any): Node =
|
||||||
|
when (argument) {
|
||||||
|
is Node -> argument
|
||||||
|
is DataFlowIR.Edge -> edgeToConstraintNode(argument)
|
||||||
|
else -> error("Unexpected argument: $argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doCall(callee: Function, arguments: List<Any>): Node {
|
||||||
|
assert(callee.parameters.size == arguments.size) {
|
||||||
|
"Function ${callee.symbol} takes ${callee.parameters.size} but caller ${function.symbol} provided ${arguments.size}"
|
||||||
|
}
|
||||||
|
callee.parameters.forEachIndexed { index, parameter ->
|
||||||
|
val argument = argumentToConstraintNode(arguments[index])
|
||||||
|
argument.addEdge(parameter)
|
||||||
|
}
|
||||||
|
return callee.returns
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doCall(callee: DataFlowIR.FunctionSymbol,
|
||||||
|
arguments: List<Any>,
|
||||||
|
returnType: DataFlowIR.Type.Declared,
|
||||||
|
receiverType: DataFlowIR.Type.Declared?): Node {
|
||||||
|
val resolvedCallee = callee.resolved()
|
||||||
|
val calleeConstraintGraph = createFunctionConstraintGraph(resolvedCallee, false)
|
||||||
|
return if (calleeConstraintGraph == null) {
|
||||||
|
constraintGraph.externalFunctions.getOrPut(resolvedCallee) {
|
||||||
|
val fictitiousReturnNode = ordinaryNode { "External$resolvedCallee" }
|
||||||
|
val possibleReturnTypes = typeHierarchy.inheritorsOf(returnType).filter { instantiatingClasses.containsKey(it) }
|
||||||
|
for (type in possibleReturnTypes) {
|
||||||
|
concreteClass(type).addEdge(fictitiousReturnNode)
|
||||||
|
}
|
||||||
|
fictitiousReturnNode
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (receiverType == null)
|
||||||
|
doCall(calleeConstraintGraph, arguments)
|
||||||
|
else {
|
||||||
|
val receiverNode = argumentToConstraintNode(arguments[0])
|
||||||
|
val castedReceiver = ordinaryNode { "CastedReceiver\$${function.symbol}" }
|
||||||
|
val castedEdge = createCastEdge(castedReceiver, receiverType)
|
||||||
|
receiverNode.addCastEdge(castedEdge)
|
||||||
|
doCall(calleeConstraintGraph, listOf(castedReceiver) + arguments.drop(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node is DataFlowIR.Node.Variable && !node.temp) {
|
||||||
|
var variableNode = variables[node]
|
||||||
|
if (variableNode == null) {
|
||||||
|
variableNode = ordinaryNode { "Variable\$${function.symbol}" }
|
||||||
|
variables[node] = variableNode
|
||||||
|
for (value in node.values) {
|
||||||
|
edgeToConstraintNode(value).addEdge(variableNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return variableNode
|
||||||
|
}
|
||||||
|
|
||||||
|
return functionNodesMap.getOrPut(node) {
|
||||||
|
when (node) {
|
||||||
|
is DataFlowIR.Node.Const ->
|
||||||
|
sourceNode(concreteType(node.type.resolved())) { "Const\$${function.symbol}" }
|
||||||
|
|
||||||
|
is DataFlowIR.Node.Parameter ->
|
||||||
|
function.parameters[node.index]
|
||||||
|
|
||||||
|
is DataFlowIR.Node.StaticCall ->
|
||||||
|
doCall(node.callee, node.arguments, node.returnType.resolved(), node.receiverType?.resolved())
|
||||||
|
|
||||||
|
is DataFlowIR.Node.NewObject -> {
|
||||||
|
val returnType = node.returnType.resolved()
|
||||||
|
val instanceNode = concreteClass(returnType)
|
||||||
|
doCall(node.callee, listOf(instanceNode) + node.arguments, returnType, null)
|
||||||
|
instanceNode
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataFlowIR.Node.VirtualCall -> {
|
||||||
|
val callee = node.callee
|
||||||
|
val receiverType = node.receiverType.resolved()
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("Virtual call")
|
||||||
|
println("Caller: ${function.symbol}")
|
||||||
|
println("Callee: $callee")
|
||||||
|
println("Receiver type: $receiverType")
|
||||||
|
}
|
||||||
|
|
||||||
|
val possibleReceiverTypes =
|
||||||
|
if (receiverType == DataFlowIR.Type.Virtual)
|
||||||
|
emptyList()
|
||||||
|
else
|
||||||
|
typeHierarchy.inheritorsOf(receiverType).filter { instantiatingClasses.containsKey(it) }
|
||||||
|
val callees = possibleReceiverTypes.map {
|
||||||
|
when (node) {
|
||||||
|
is DataFlowIR.Node.VtableCall ->
|
||||||
|
it.vtable[node.calleeVtableIndex]
|
||||||
|
|
||||||
|
is DataFlowIR.Node.ItableCall ->
|
||||||
|
it.itable[node.calleeHash]!!
|
||||||
|
|
||||||
|
else -> error("Unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_OUTPUT(0) {
|
||||||
|
println("Possible callees:")
|
||||||
|
callees.forEach { println("$it") }
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
val returnType = node.returnType.resolved()
|
||||||
|
val receiverNode = edgeToConstraintNode(node.arguments[0])
|
||||||
|
val castedReceiver = ordinaryNode { "CastedReceiver\$${function.symbol}" }
|
||||||
|
val castedEdge = createCastEdge(castedReceiver, receiverType)
|
||||||
|
receiverNode.addCastEdge(castedEdge)
|
||||||
|
val arguments = listOf(castedReceiver) + node.arguments.drop(1)
|
||||||
|
|
||||||
|
val returnsNode = ordinaryNode { "VirtualCallReturns\$${function.symbol}" }
|
||||||
|
callees.forEachIndexed { index, actualCallee ->
|
||||||
|
doCall(actualCallee, arguments, returnType, possibleReceiverTypes[index]).addEdge(returnsNode)
|
||||||
|
}
|
||||||
|
// 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.
|
||||||
|
val virtualTypeFilter = BitSet().apply { set(VIRTUAL_TYPE_ID) }
|
||||||
|
if (!returnType.isFinal) {
|
||||||
|
receiverNode.addCastEdge(Node.CastEdge(returnsNode, virtualTypeFilter))
|
||||||
|
}
|
||||||
|
// And write to some array anything. TODO: This is conservative.
|
||||||
|
receiverNode.addCastEdge(Node.CastEdge(fieldNode(constraintGraph.arrayItemField), virtualTypeFilter))
|
||||||
|
|
||||||
|
if (callees.isEmpty() && returnType.isFinal && entryPoint == null) {
|
||||||
|
// 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.
|
||||||
|
concreteClass(returnType).addEdge(returnsNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
val devirtualizedCallees = possibleReceiverTypes.mapIndexed { index, possibleReceiverType ->
|
||||||
|
DevirtualizedCallee(possibleReceiverType, callees[index])
|
||||||
|
}
|
||||||
|
constraintGraph.virtualCallSiteReceivers[node] = VirtualCallSiteReceivers(castedReceiver, function.symbol, devirtualizedCallees)
|
||||||
|
returnsNode
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataFlowIR.Node.Singleton -> {
|
||||||
|
val type = node.type.resolved()
|
||||||
|
val instanceNode = concreteClass(type)
|
||||||
|
node.constructor?.let { doCall(it, listOf(instanceNode), type, null) }
|
||||||
|
instanceNode
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataFlowIR.Node.FieldRead ->
|
||||||
|
fieldNode(node.field)
|
||||||
|
|
||||||
|
is DataFlowIR.Node.FieldWrite -> {
|
||||||
|
val fieldNode = fieldNode(node.field)
|
||||||
|
edgeToConstraintNode(node.value).addEdge(fieldNode)
|
||||||
|
constraintGraph.voidNode
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataFlowIR.Node.ArrayRead ->
|
||||||
|
fieldNode(constraintGraph.arrayItemField)
|
||||||
|
|
||||||
|
is DataFlowIR.Node.ArrayWrite -> {
|
||||||
|
val fieldNode = fieldNode(constraintGraph.arrayItemField)
|
||||||
|
edgeToConstraintNode(node.value).addEdge(fieldNode)
|
||||||
|
constraintGraph.voidNode
|
||||||
|
}
|
||||||
|
|
||||||
|
is DataFlowIR.Node.Variable ->
|
||||||
|
node.values.map { edgeToConstraintNode(it) }.let { values ->
|
||||||
|
ordinaryNode { "TempVar\$${function.symbol}" }.also { node ->
|
||||||
|
values.forEach { it.addEdge(node) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("Unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevirtualizedCallee(val receiverType: DataFlowIR.Type, val callee: DataFlowIR.FunctionSymbol)
|
||||||
|
|
||||||
|
class DevirtualizedCallSite(val possibleCallees: List<DevirtualizedCallee>)
|
||||||
|
|
||||||
|
fun analyze(context: Context, moduleDFG: ModuleDFG, externalModulesDFG: ExternalModulesDFG)
|
||||||
|
: Map<DataFlowIR.Node.VirtualCall, DevirtualizedCallSite> {
|
||||||
|
return DevirtualizationAnalysis(context, moduleDFG, externalModulesDFG).analyze()
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -275,7 +275,7 @@ internal object EscapeAnalysis {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (multiNode in condensation.topologicalOrder)
|
for (multiNode in condensation.topologicalOrder.reversed())
|
||||||
analyze(callGraph, multiNode)
|
analyze(callGraph, multiNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user