[K/N][optmz] Fixed a problem with call graph

Previously it was considered that function references leaking to
the native world are being called at their definitions. This is wrong,
as it is impossible to deduce from the Kotlin code alone when they
will be called, so just simply assume them a part of the root set.
This commit is contained in:
Igor Chevdar
2022-10-31 12:21:04 +02:00
committed by Space Team
parent df371e8f17
commit c6ef3d849e
2 changed files with 34 additions and 33 deletions
@@ -131,15 +131,6 @@ internal class CallGraphBuilder(
node
)
is DataFlowIR.Node.FunctionReference ->
block(DataFlowIR.Node.Call(
callee = node.symbol,
arguments = emptyList(),
returnType = node.symbol.returnParameter.type,
irCallSite = null),
node
)
else -> { }
}
}
@@ -65,50 +65,60 @@ internal object DevirtualizationAnalysis {
}
val entryPoint = context.ir.symbols.entryPoint?.owner
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.symbolTable.functionMap.values.filter {
it is DataFlowIR.FunctionSymbol.Public
|| (it as? DataFlowIR.FunctionSymbol.External)?.isExported == true
} +
moduleDFG.symbolTable.classMap.values
.filterIsInstance<DataFlowIR.Type.Declared>()
.flatMap { it.vtable + it.itable.values.flatten() }
.filterIsInstance<DataFlowIR.FunctionSymbol.Declared>()
.filter { moduleDFG.functions.containsKey(it) }
}
val exported = 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.symbolTable.functionMap.values.filter {
it is DataFlowIR.FunctionSymbol.Public
|| (it as? DataFlowIR.FunctionSymbol.External)?.isExported == true
} +
moduleDFG.symbolTable.classMap.values
.filterIsInstance<DataFlowIR.Type.Declared>()
.flatMap { it.vtable + it.itable.values.flatten() }
.filterIsInstance<DataFlowIR.FunctionSymbol.Declared>()
.filter { moduleDFG.functions.containsKey(it) }
}
// TODO: Are globals initializers always called whether they are actually reachable from roots or not?
// TODO: With the changed semantics of global initializers this is no longer the case - rework.
val globalInitializers =
moduleDFG.symbolTable.functionMap.values.filter { it.isTopLevelFieldInitializer || it.isGlobalInitializer } +
externalModulesDFG.functionDFGs.keys.filter { it.isTopLevelFieldInitializer || it.isGlobalInitializer }
externalModulesDFG.functionDFGs.keys.filter { it.isTopLevelFieldInitializer || it.isGlobalInitializer }
val explicitlyExportedFunctions =
val explicitlyExported =
moduleDFG.symbolTable.functionMap.values.filter { it.explicitlyExported } +
externalModulesDFG.functionDFGs.keys.filter { it.explicitlyExported }
externalModulesDFG.functionDFGs.keys.filter { it.explicitlyExported }
// Conservatively assume each associated object could be called.
// Note: for constructors there is additional parameter (<this>) and its type will be added
// to instantiating classes since all objects are final types.
val associatedObjects = mutableListOf<DataFlowIR.FunctionSymbol>()
context.irModule!!.acceptChildrenVoid(object: IrElementVisitorVoid {
val associatedObjectConstructors = mutableListOf<DataFlowIR.FunctionSymbol>()
// At this point all function references are lowered except those leaking to the native world.
// Conservatively assume them belonging of the root set.
val leakingThroughFunctionReferences = mutableListOf<DataFlowIR.FunctionSymbol>()
context.irModule!!.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitClass(declaration: IrClass) {
declaration.acceptChildrenVoid(this)
context.getLayoutBuilder(declaration).associatedObjects.values.forEach {
assert (it.kind == ClassKind.OBJECT) { "An object expected but was ${it.dump()}" }
associatedObjects += moduleDFG.symbolTable.mapFunction(it.constructors.single())
assert(it.kind == ClassKind.OBJECT) { "An object expected but was ${it.dump()}" }
associatedObjectConstructors += moduleDFG.symbolTable.mapFunction(it.constructors.single())
}
super.visitClass(declaration)
}
override fun visitFunctionReference(expression: IrFunctionReference) {
expression.acceptChildrenVoid(this)
leakingThroughFunctionReferences.add(moduleDFG.symbolTable.mapFunction(expression.symbol.owner))
}
})
return (exportedFunctions + globalInitializers + explicitlyExportedFunctions + associatedObjects).distinct()
return (exported + globalInitializers + explicitlyExported + associatedObjectConstructors + leakingThroughFunctionReferences).distinct()
}
fun BitSet.format(allTypes: Array<DataFlowIR.Type.Declared>): String {