Rewrote escape analysis to use DFG

This commit is contained in:
Igor Chevdar
2017-12-15 00:36:08 +03:00
parent 888cd58bf7
commit 83b02ea5d0
14 changed files with 477 additions and 1021 deletions
@@ -89,7 +89,6 @@ internal fun produceOutput(context: Context) {
llvmModule,
nopack,
manifest,
context.escapeAnalysisResult.getValueOrNull()?.build()?.toByteArray(),
context.dataFlowGraph)
context.library = library
@@ -176,7 +176,6 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
// But we have to wait until the code generation phase,
// to dump this information into generated file.
var serializedLinkData: LinkData? = null
val escapeAnalysisResult = lazy { ModuleEscapeAnalysisResult.ModuleEAResult.newBuilder() }
var dataFlowGraph: ByteArray? = null
@Deprecated("")
@@ -1,15 +0,0 @@
syntax = "proto2";
package org.jetbrains.kotlin.backend.konan;
option java_outer_classname = "ModuleEscapeAnalysisResult";
option optimize_for = LITE_RUNTIME;
message FunctionEAResult {
required string fqName = 1;
required int32 escapes = 2;
repeated int32 pointsTo = 3;
}
message ModuleEAResult {
repeated FunctionEAResult functionEAResults = 1;
}
@@ -31,7 +31,7 @@ internal class DirectedGraphMultiNode<out K>(val nodes: Set<K>)
internal class DirectedGraphCondensation<out K>(val topologicalOrder: List<DirectedGraphMultiNode<K>>)
internal class DirectedGraphCondensationBuilder<K, out N: DirectedGraphNode<K>>(val graph: DirectedGraph<K, N>) {
internal class DirectedGraphCondensationBuilder<K, out N: DirectedGraphNode<K>>(private val graph: DirectedGraph<K, N>) {
private val visited = mutableSetOf<K>()
private val order = mutableListOf<N>()
private val nodeToMultiNodeMap = mutableMapOf<N, DirectedGraphMultiNode<K>>()
@@ -44,8 +44,6 @@ interface KonanLibraryLayout {
get() = File(libDir, "linkdata")
val moduleHeaderFile
get() = File(linkdataDir, "module")
val escapeAnalysisFile
get() = File(linkdataDir, "module_escape_analysis")
val dataFlowGraphFile
get() = File(linkdataDir, "module_data_flow_graph")
fun packageFile(packageName: String)
@@ -28,7 +28,6 @@ interface KonanLibraryReader {
val linkerOpts: List<String>
val unresolvedDependencies: List<String>
val dataFlowGraph: ByteArray?
val escapeAnalysis: ByteArray?
val isDefaultLibrary: Boolean get() = false
val isNeededForLink: Boolean get() = true
val manifestProperties: Properties
@@ -25,7 +25,6 @@ interface KonanLibraryWriter {
fun addKotlinBitcode(llvmModule: LLVMModuleRef)
fun addLinkDependencies(libraries: List<KonanLibraryReader>)
fun addManifestAddend(path: String)
fun addEscapeAnalysis(escapeAnalysis: ByteArray)
fun addDataFlowGraph(dataFlowGraph: ByteArray)
val mainBitcodeFileName: String
fun commit()
@@ -51,7 +51,6 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int,
}
val targetList = inPlace.targetsDir.listFiles.map{it.name}
override val escapeAnalysis by lazy { inPlace.escapeAnalysisFile.let { if (it.exists) it.readBytes() else null } }
override val dataFlowGraph by lazy { inPlace.dataFlowGraphFile.let { if (it.exists) it.readBytes() else null } }
override val libraryName
@@ -101,10 +101,6 @@ class LibraryWriterImpl(override val libDir: File, moduleName: String, currentAb
manifestProperties.putAll(properties)
}
override fun addEscapeAnalysis(escapeAnalysis: ByteArray) {
escapeAnalysisFile.writeBytes(escapeAnalysis)
}
override fun addDataFlowGraph(dataFlowGraph: ByteArray) {
dataFlowGraphFile.writeBytes(dataFlowGraph)
}
@@ -130,7 +126,6 @@ internal fun buildLibrary(
llvmModule: LLVMModuleRef,
nopack: Boolean,
manifest: String?,
escapeAnalysis: ByteArray?,
dataFlowGraph: ByteArray?): KonanLibraryWriter {
val library = LibraryWriterImpl(output, moduleName, abiVersion, target, nopack)
@@ -145,7 +140,6 @@ internal fun buildLibrary(
}
manifest ?.let { library.addManifestAddend(it) }
library.addLinkDependencies(linkDependencies)
escapeAnalysis?.let { library.addEscapeAnalysis(it) }
dataFlowGraph?.let { library.addDataFlowGraph(it) }
library.commit()
@@ -24,10 +24,8 @@ import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.library.impl.buildLibrary
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
import org.jetbrains.kotlin.backend.konan.optimizations.*
import org.jetbrains.kotlin.backend.konan.util.getValueOrNull
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
@@ -94,7 +92,8 @@ internal fun emitLLVM(context: Context) {
val lifetimes = mutableMapOf<IrElement, Lifetime>()
val codegenVisitor = CodeGeneratorVisitor(context, lifetimes)
phaser.phase(KonanPhase.ESCAPE_ANALYSIS) {
EscapeAnalysis.computeLifetimes(irModule, context, codegenVisitor.codegen, lifetimes)
val callGraph = CallGraphBuilder(context, moduleDFG!!, externalModulesDFG!!).build()
EscapeAnalysis.computeLifetimes(moduleDFG!!, externalModulesDFG!!, callGraph, lifetimes)
}
phaser.phase(KonanPhase.CODEGEN) {
@@ -0,0 +1,113 @@
package org.jetbrains.kotlin.backend.konan.optimizations
import org.jetbrains.kotlin.backend.konan.DirectedGraph
import org.jetbrains.kotlin.backend.konan.DirectedGraphNode
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
import org.jetbrains.kotlin.backend.konan.llvm.findMainEntryPoint
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
internal class CallGraphNode(val graph: CallGraph, val symbol: DataFlowIR.FunctionSymbol)
: DirectedGraphNode<DataFlowIR.FunctionSymbol> {
override val key get() = symbol
override val directEdges: List<DataFlowIR.FunctionSymbol> by lazy {
graph.directEdges[symbol]!!.callSites
.map { it.actualCallee }
.filter { graph.reversedEdges.containsKey(it) }
}
override val reversedEdges: List<DataFlowIR.FunctionSymbol> by lazy {
graph.reversedEdges[symbol]!!
}
class CallSite(val call: DataFlowIR.Node.Call, val actualCallee: DataFlowIR.FunctionSymbol)
val callSites = mutableListOf<CallSite>()
}
internal class CallGraph(val directEdges: Map<DataFlowIR.FunctionSymbol, CallGraphNode>,
val reversedEdges: Map<DataFlowIR.FunctionSymbol, MutableList<DataFlowIR.FunctionSymbol>>)
: DirectedGraph<DataFlowIR.FunctionSymbol, CallGraphNode> {
override val nodes get() = directEdges.values
override fun get(key: DataFlowIR.FunctionSymbol) = directEdges[key]!!
fun addEdge(caller: DataFlowIR.FunctionSymbol, callSite: CallGraphNode.CallSite) {
directEdges[caller]!!.callSites += callSite
reversedEdges[callSite.actualCallee]?.add(caller)
}
}
internal class CallGraphBuilder(val context: Context,
val moduleDFG: ModuleDFG,
val externalModulesDFG: ExternalModulesDFG) {
private val DEBUG = 0
private inline fun DEBUG_OUTPUT(severity: Int, block: () -> Unit) {
if (DEBUG > severity) block()
}
private val hasMain = context.config.configuration.get(KonanConfigKeys.PRODUCE) == CompilerOutputKind.PROGRAM
private val symbolTable = moduleDFG.symbolTable
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 fun DataFlowIR.Type.Declared.isSubtypeOf(other: DataFlowIR.Type.Declared): Boolean {
return this == other || this.superTypes.any { it.resolved().isSubtypeOf(other) }
}
private val directEdges = mutableMapOf<DataFlowIR.FunctionSymbol, CallGraphNode>()
private val reversedEdges = mutableMapOf<DataFlowIR.FunctionSymbol, MutableList<DataFlowIR.FunctionSymbol>>()
private val callGraph = CallGraph(directEdges, reversedEdges)
fun build(): CallGraph {
val rootSet = if (hasMain) {
listOf(symbolTable.mapFunction(findMainEntryPoint(context)!!).resolved()) +
moduleDFG.functions
.filter { it.value.isGlobalInitializer }
.map { it.key }
} else {
moduleDFG.functions.keys.filterIsInstance<DataFlowIR.FunctionSymbol.Public>()
}
@Suppress("LoopToCallChain")
for (symbol in rootSet) {
if (!directEdges.containsKey(symbol))
dfs(symbol)
}
return callGraph
}
private fun dfs(symbol: DataFlowIR.FunctionSymbol) {
val node = CallGraphNode(callGraph, symbol)
directEdges.put(symbol, node)
val list = mutableListOf<DataFlowIR.FunctionSymbol>()
reversedEdges.put(symbol, list)
val function = moduleDFG.functions[symbol] ?: externalModulesDFG.functionDFGs[symbol]
val body = function!!.body
body.nodes.filterIsInstance<DataFlowIR.Node.Call>()
.forEach {
val callee = it.callee.resolved()
callGraph.addEdge(symbol, CallGraphNode.CallSite(it, callee))
if (callee is DataFlowIR.FunctionSymbol.Declared
&& it !is DataFlowIR.Node.VirtualCall
&& !directEdges.containsKey(callee))
dfs(callee)
}
}
}
@@ -262,13 +262,16 @@ internal object DFGSerializer {
}
}
class ExternalFunctionSymbol(val hash: Long, val numberOfParameters: Int, val name: String?) {
class ExternalFunctionSymbol(val hash: Long, val numberOfParameters: Int, val escapes: Int?, val pointsTo: IntArray?, val name: String?) {
constructor(data: ArraySlice) : this(data.readLong(), data.readInt(), data.readNullableString())
constructor(data: ArraySlice) : this(data.readLong(), data.readInt(), data.readNullableInt(),
data.readNullable { readIntArray() }, data.readNullableString())
fun write(result: ArraySlice) {
result.writeLong(hash)
result.writeInt(numberOfParameters)
result.writeNullableInt(escapes)
result.writeNullable(pointsTo) { writeIntArray(it) }
result.writeNullableString(name)
}
}
@@ -313,8 +316,8 @@ internal object DFGSerializer {
}
companion object {
fun external(hash: Long, numberOfParameters: Int, name: String?) =
FunctionSymbol(ExternalFunctionSymbol(hash, numberOfParameters, name), null, null)
fun external(hash: Long, numberOfParameters: Int, escapes: Int?, pointsTo: IntArray?, name: String?) =
FunctionSymbol(ExternalFunctionSymbol(hash, numberOfParameters, escapes, pointsTo, name), null, null)
fun public(hash: Long, numberOfParameters: Int, index: Int, name: String?) =
FunctionSymbol(null, PublicFunctionSymbol(hash, numberOfParameters, index, name), null)
@@ -675,7 +678,7 @@ internal object DFGSerializer {
val name = functionSymbol.name
when (functionSymbol) {
is DataFlowIR.FunctionSymbol.External ->
FunctionSymbol.external(functionSymbol.hash, numberOfParameters, name)
FunctionSymbol.external(functionSymbol.hash, numberOfParameters, functionSymbol.escapes, functionSymbol.pointsTo, name)
is DataFlowIR.FunctionSymbol.Public ->
FunctionSymbol.public(functionSymbol.hash, numberOfParameters, functionSymbol.symbolTableIndex, name)
@@ -817,7 +820,7 @@ internal object DFGSerializer {
val private = it.private
when {
external != null ->
DataFlowIR.FunctionSymbol.External(external.hash, external.numberOfParameters, external.name)
DataFlowIR.FunctionSymbol.External(external.hash, external.numberOfParameters, external.escapes, external.pointsTo, external.name)
public != null -> {
val symbolTableIndex = public.index
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.isExported
import org.jetbrains.kotlin.backend.konan.llvm.localHash
import org.jetbrains.kotlin.backend.konan.llvm.symbolName
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrField
@@ -22,6 +23,8 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.IntValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.types.KotlinType
@@ -96,7 +99,7 @@ internal object DataFlowIR {
}
abstract class FunctionSymbol(val numberOfParameters: Int, val name: String?) {
class External(val hash: Long, numberOfParameters: Int, name: String? = null)
class External(val hash: Long, numberOfParameters: Int, val escapes: Int?, val pointsTo: IntArray?, name: String? = null)
: FunctionSymbol(numberOfParameters, name) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -110,7 +113,7 @@ internal object DataFlowIR {
}
override fun toString(): String {
return "ExternalFunction(hash='$hash', name='$name')"
return "ExternalFunction(hash='$hash', name='$name', escapes='$escapes', pointsTo='${pointsTo?.contentToString()}')"
}
}
@@ -211,109 +214,8 @@ internal object DataFlowIR {
val body: FunctionBody) {
fun printNode(node: Node, ids: Map<Node, Int>) {
when (node) {
is Node.Const ->
println(" CONST ${node.type}")
is Node.Parameter ->
println(" PARAM ${node.index}")
is Node.Singleton ->
println(" SINGLETON ${node.type}")
is Node.StaticCall -> {
println(" STATIC CALL ${node.callee}")
node.arguments.forEach {
print(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
println()
else
println(" CASTED TO ${it.castToType}")
}
}
is Node.VtableCall -> {
println(" VIRTUAL CALL ${node.callee}")
println(" RECEIVER: ${node.receiverType}")
println(" VTABLE INDEX: ${node.calleeVtableIndex}")
node.arguments.forEach {
print(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
println()
else
println(" CASTED TO ${it.castToType}")
}
}
is Node.ItableCall -> {
println(" INTERFACE CALL ${node.callee}")
println(" RECEIVER: ${node.receiverType}")
println(" METHOD HASH: ${node.calleeHash}")
node.arguments.forEach {
print(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
println()
else
println(" CASTED TO ${it.castToType}")
}
}
is Node.NewObject -> {
println(" NEW OBJECT ${node.callee}")
println(" TYPE ${node.returnType}")
node.arguments.forEach {
print(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
println()
else
println(" CASTED TO ${it.castToType}")
}
}
is Node.FieldRead -> {
println(" FIELD READ ${node.field}")
print(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
if (node.receiver?.castToType == null)
println()
else
println(" CASTED TO ${node.receiver.castToType}")
}
is Node.FieldWrite -> {
println(" FIELD WRITE ${node.field}")
print(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
if (node.receiver?.castToType == null)
println()
else
println(" CASTED TO ${node.receiver.castToType}")
print(" VALUE #${ids[node.value.node]!!}")
if (node.value.castToType == null)
println()
else
println(" CASTED TO ${node.value.castToType}")
}
is Node.Variable -> {
println(" ${if (node.temp) "TEMP VAR" else "VARIABLE"} ")
node.values.forEach {
print(" VAL #${ids[it.node]!!}")
if (it.castToType == null)
println()
else
println(" CASTED TO ${it.castToType}")
}
}
else -> {
println(" UNKNOWN: ${node::class.java}")
}
}
}
fun debugOutput() {
println("FUNCTION TEMPLATE $symbol")
println("FUNCTION $symbol")
println("Params: $numberOfParameters")
val ids = body.nodes.withIndex().associateBy({ it.value }, { it.index })
body.nodes.forEach {
@@ -323,6 +225,122 @@ internal object DataFlowIR {
println(" RETURNS")
printNode(body.returns, ids)
}
companion object {
fun printNode(node: Node, ids: Map<Node, Int>) = print(nodeToString(node, ids))
fun nodeToString(node: Node, ids: Map<Node, Int>) = when (node) {
is Node.Const ->
" CONST ${node.type}\n"
is Node.Parameter ->
" PARAM ${node.index}\n"
is Node.Singleton ->
" SINGLETON ${node.type}\n"
is Node.StaticCall -> {
val result = StringBuilder()
result.appendln(" STATIC CALL ${node.callee}")
node.arguments.forEach {
result.append(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${it.castToType}")
}
result.toString()
}
is Node.VtableCall -> {
val result = StringBuilder()
result.appendln(" VIRTUAL CALL ${node.callee}")
result.appendln(" RECEIVER: ${node.receiverType}")
result.appendln(" VTABLE INDEX: ${node.calleeVtableIndex}")
node.arguments.forEach {
result.append(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${it.castToType}")
}
result.toString()
}
is Node.ItableCall -> {
val result = StringBuilder()
result.appendln(" INTERFACE CALL ${node.callee}")
result.appendln(" RECEIVER: ${node.receiverType}")
result.appendln(" METHOD HASH: ${node.calleeHash}")
node.arguments.forEach {
result.append(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${it.castToType}")
}
result.toString()
}
is Node.NewObject -> {
val result = StringBuilder()
result.appendln(" NEW OBJECT ${node.callee}")
result.appendln(" TYPE ${node.returnType}")
node.arguments.forEach {
result.append(" ARG #${ids[it.node]!!}")
if (it.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${it.castToType}")
}
result.toString()
}
is Node.FieldRead -> {
val result = StringBuilder()
result.appendln(" FIELD READ ${node.field}")
result.append(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
if (node.receiver?.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${node.receiver.castToType}")
result.toString()
}
is Node.FieldWrite -> {
val result = StringBuilder()
result.appendln(" FIELD WRITE ${node.field}")
result.append(" RECEIVER #${node.receiver?.node?.let { ids[it]!! } ?: "null"}")
if (node.receiver?.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${node.receiver.castToType}")
print(" VALUE #${ids[node.value.node]!!}")
if (node.value.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${node.value.castToType}")
result.toString()
}
is Node.Variable -> {
val result = StringBuilder()
result.appendln(" ${if (node.temp) "TEMP VAR" else "VARIABLE"} ")
node.values.forEach {
result.append(" VAL #${ids[it.node]!!}")
if (it.castToType == null)
result.appendln()
else
result.appendln(" CASTED TO ${it.castToType}")
}
result.toString()
}
else -> {
" UNKNOWN: ${node::class.java}\n"
}
}
}
}
class SymbolTable(val context: Context, val irModule: IrModuleFragment, val module: Module) {
@@ -334,6 +352,21 @@ internal object DataFlowIR {
val classMap = mutableMapOf<ClassDescriptor, Type>()
val functionMap = mutableMapOf<CallableDescriptor, FunctionSymbol>()
private val NAME_ESCAPES = Name.identifier("Escapes")
private val NAME_POINTS_TO = Name.identifier("PointsTo")
private val FQ_NAME_KONAN = FqName.fromSegments(listOf("konan"))
private val FQ_NAME_ESCAPES = FQ_NAME_KONAN.child(NAME_ESCAPES)
private val FQ_NAME_POINTS_TO = FQ_NAME_KONAN.child(NAME_POINTS_TO)
private val konanPackage = context.builtIns.builtInsModule.getPackage(FQ_NAME_KONAN).memberScope
private val escapesAnnotationDescriptor = konanPackage.getContributedClassifier(
NAME_ESCAPES, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
private val escapesWhoDescriptor = escapesAnnotationDescriptor.unsubstitutedPrimaryConstructor!!.valueParameters.single()
private val pointsToAnnotationDescriptor = konanPackage.getContributedClassifier(
NAME_POINTS_TO, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
private val pointsToOnWhomDescriptor = pointsToAnnotationDescriptor.unsubstitutedPrimaryConstructor!!.valueParameters.single()
var privateTypeIndex = 0
var privateFunIndex = 0
var couldBeCalledVirtuallyIndex = 0
@@ -427,9 +460,16 @@ internal object DataFlowIR {
is FunctionDescriptor -> {
val name = if (it.isExported()) it.symbolName else it.internalName
val numberOfParameters = it.allParameters.size + if (it.isSuspend) 1 else 0
if (it.module != irModule.descriptor || it.externalOrIntrinsic())
FunctionSymbol.External(name.localHash.value, numberOfParameters, takeName { name })
else {
if (it.module != irModule.descriptor || it.externalOrIntrinsic()) {
val escapesAnnotation = it.annotations.findAnnotation(FQ_NAME_ESCAPES)
val pointsToAnnotation = it.annotations.findAnnotation(FQ_NAME_POINTS_TO)
@Suppress("UNCHECKED_CAST")
val escapesBitMask = (escapesAnnotation?.allValueArguments?.get(escapesWhoDescriptor.name) as? ConstantValue<Int>)?.value
@Suppress("UNCHECKED_CAST")
val pointsToBitMask = (pointsToAnnotation?.allValueArguments?.get(pointsToOnWhomDescriptor.name) as? ConstantValue<List<IntValue>>)?.value
FunctionSymbol.External(name.localHash.value, numberOfParameters, escapesBitMask,
pointsToBitMask?.let { it.map { it.value }.toIntArray() }, takeName { name })
} else {
val isAbstract = it.modality == Modality.ABSTRACT
val classDescriptor = it.containingDeclaration as? ClassDescriptor
val placeToFunctionsTable = !isAbstract && it !is ConstructorDescriptor && classDescriptor != null