From c6ef3d849ed0712eb9fa10284878d081b06879e2 Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Mon, 31 Oct 2022 12:21:04 +0200 Subject: [PATCH] [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. --- .../konan/optimizations/CallGraphBuilder.kt | 9 --- .../optimizations/DevirtualizationAnalysis.kt | 58 +++++++++++-------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt index b7fd2645bcd..56527365a7a 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/CallGraphBuilder.kt @@ -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 -> { } } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DevirtualizationAnalysis.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DevirtualizationAnalysis.kt index 96bef0db86c..569ed92f307 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DevirtualizationAnalysis.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DevirtualizationAnalysis.kt @@ -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() - .flatMap { it.vtable + it.itable.values.flatten() } - .filterIsInstance() - .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() + .flatMap { it.vtable + it.itable.values.flatten() } + .filterIsInstance() + .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 () and its type will be added // to instantiating classes since all objects are final types. - val associatedObjects = mutableListOf() - context.irModule!!.acceptChildrenVoid(object: IrElementVisitorVoid { + val associatedObjectConstructors = mutableListOf() + // 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() + 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): String {