From a6bf1e726275b373cfd3245af8714d15651fdaf5 Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Wed, 23 May 2018 17:37:51 +0300 Subject: [PATCH] Made escape analysis up to date with devirtualization --- .../kotlin/backend/konan/KonanPhases.kt | 4 +- .../kotlin/backend/konan/ir/KonanIr.kt | 2 + .../kotlin/backend/konan/llvm/ContextUtils.kt | 57 +++---- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 4 +- .../backend/konan/optimizations/DFGBuilder.kt | 3 +- .../konan/optimizations/DFGSerializer.kt | 31 ++-- .../backend/konan/optimizations/DataFlowIR.kt | 19 ++- .../konan/optimizations/Devirtualization.kt | 3 +- .../konan/optimizations/EscapeAnalysis.kt | 160 +++++++++++++----- 9 files changed, 177 insertions(+), 106 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index 1685dc86c73..ecf7af8c3ce 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -61,10 +61,10 @@ enum class KonanPhase(val description: String, /* ... */ BITCODE("LLVM BitCode Generation"), /* ... ... */ RTTI("RTTI Generation"), /* ... ... */ BUILD_DFG("Data flow graph building"), - /* ... ... */ SERIALIZE_DFG("Data flow graph serializing", BUILD_DFG), /* ... ... */ 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), // TODO: Requires devirtualization. + /* ... ... */ SERIALIZE_DFG("Data flow graph serializing", BUILD_DFG), // TODO: Requires escape analysis. /* ... ... */ CODEGEN("Code Generation"), /* ... ... */ BITCODE_LINKER("Bitcode linking"), /* */ LINK_STAGE("Link stage"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/KonanIr.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/KonanIr.kt index 58f8687812a..9aab4ac9388 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/KonanIr.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/KonanIr.kt @@ -189,6 +189,7 @@ class IrSuspendableExpressionImpl(startOffset: Int, endOffset: Int, type: Kotlin } internal interface IrPrivateFunctionCall : IrCall { + val virtualCallee: IrCall? val dfgSymbol: DataFlowIR.FunctionSymbol.Declared val moduleDescriptor: ModuleDescriptor val totalFunctions: Int @@ -200,6 +201,7 @@ internal class IrPrivateFunctionCallImpl(startOffset: Int, type: KotlinType, override val symbol: IrFunctionSymbol, override val descriptor: FunctionDescriptor, + override val virtualCallee: IrCall?, typeArgumentsCount: Int, override val dfgSymbol: DataFlowIR.FunctionSymbol.Declared, override val moduleDescriptor: ModuleDescriptor, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index dd40e9c6ee7..59da38f6533 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -40,45 +40,39 @@ import kotlin.reflect.KProperty internal sealed class SlotType { // Frame local arena slot can be used. - class ARENA: SlotType() + object ARENA: SlotType() // Return slot can be used. - class RETURN: SlotType() - // Return slot, if it is an arena, can be used. - class RETURN_IF_ARENA: SlotType() + object RETURN: SlotType() // Return slot, if it is an arena, can be used. + object RETURN_IF_ARENA: SlotType() + // Param slot, if it is an arena, can be used. class PARAM_IF_ARENA(val parameter: Int): SlotType() + // Params slot, if it is an arena, can be used. + class PARAMS_IF_ARENA(val parameters: IntArray, val useReturnSlot: Boolean): SlotType() // Anonymous slot. - class ANONYMOUS: SlotType() + object ANONYMOUS: SlotType() // Unknown slot type. - class UNKNOWN: SlotType() - - companion object { - val ARENA = ARENA() - val RETURN = RETURN() - val RETURN_IF_ARENA = RETURN_IF_ARENA() - val ANONYMOUS = ANONYMOUS() - val UNKNOWN = UNKNOWN() - } + object UNKNOWN: SlotType() } // Lifetimes class of reference, computed by escape analysis. internal sealed class Lifetime(val slotType: SlotType) { // If reference is frame-local (only obtained from some call and never leaves). - class LOCAL: Lifetime(SlotType.ARENA) { + object LOCAL: Lifetime(SlotType.ARENA) { override fun toString(): String { return "LOCAL" } } // If reference is only returned. - class RETURN_VALUE: Lifetime(SlotType.RETURN) { + object RETURN_VALUE: Lifetime(SlotType.RETURN) { override fun toString(): String { return "RETURN_VALUE" } } // If reference is set as field of references of class RETURN_VALUE or INDIRECT_RETURN_VALUE. - class INDIRECT_RETURN_VALUE: Lifetime(SlotType.RETURN_IF_ARENA) { + object INDIRECT_RETURN_VALUE: Lifetime(SlotType.RETURN_IF_ARENA) { override fun toString(): String { return "INDIRECT_RETURN_VALUE" } @@ -91,15 +85,23 @@ internal sealed class Lifetime(val slotType: SlotType) { } } + // If reference is stored to the field of an incoming parameters. + class PARAMETERS_FIELD(val parameters: IntArray, val useReturnSlot: Boolean) + : Lifetime(SlotType.PARAMS_IF_ARENA(parameters, useReturnSlot)) { + override fun toString(): String { + return "PARAMETERS_FIELD(${parameters.contentToString()}, useReturnSlot='$useReturnSlot')" + } + } + // If reference refers to the global (either global object or global variable). - class GLOBAL: Lifetime(SlotType.ANONYMOUS) { + object GLOBAL: Lifetime(SlotType.ANONYMOUS) { override fun toString(): String { return "GLOBAL" } } // If reference used to throw. - class THROW: Lifetime(SlotType.ANONYMOUS) { + object THROW: Lifetime(SlotType.ANONYMOUS) { override fun toString(): String { return "THROW" } @@ -107,36 +109,25 @@ internal sealed class Lifetime(val slotType: SlotType) { // If reference used as an argument of outgoing function. Class can be improved by escape analysis // of called function. - class ARGUMENT: Lifetime(SlotType.ANONYMOUS) { + object ARGUMENT: Lifetime(SlotType.ANONYMOUS) { override fun toString(): String { return "ARGUMENT" } } // If reference class is unknown. - class UNKNOWN: Lifetime(SlotType.UNKNOWN) { + object UNKNOWN: Lifetime(SlotType.UNKNOWN) { override fun toString(): String { return "UNKNOWN" } } // If reference class is irrelevant. - class IRRELEVANT: Lifetime(SlotType.UNKNOWN) { + object IRRELEVANT: Lifetime(SlotType.UNKNOWN) { override fun toString(): String { return "IRRELEVANT" } } - - companion object { - val LOCAL = LOCAL() - val RETURN_VALUE = RETURN_VALUE() - val INDIRECT_RETURN_VALUE = INDIRECT_RETURN_VALUE() - val GLOBAL = GLOBAL() - val THROW = THROW() - val ARGUMENT = ARGUMENT() - val UNKNOWN = UNKNOWN() - val IRRELEVANT = IRRELEVANT() - } } /** diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index c0b156d7819..0a310576c5a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -119,7 +119,7 @@ internal fun emitLLVM(context: Context) { phaser.phase(KonanPhase.ESCAPE_ANALYSIS) { val callGraph = CallGraphBuilder(context, moduleDFG!!, externalModulesDFG!!, devirtualizationAnalysisResult, false).build() - EscapeAnalysis.computeLifetimes(moduleDFG!!, externalModulesDFG!!, callGraph, lifetimes) + EscapeAnalysis.computeLifetimes(context, moduleDFG!!, externalModulesDFG!!, callGraph, lifetimes) } phaser.phase(KonanPhase.SERIALIZE_DFG) { @@ -1993,7 +1993,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt index 7df14305b04..998cb35c7c6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -661,7 +661,8 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag receiverType, name.localHash.value, takeName { name } - ) + ), + value ) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt index c26f91b5d18..2b69a5fa362 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt @@ -309,26 +309,25 @@ internal object DFGSerializer { } } - class FunctionSymbolBase(val parameterTypes: IntArray, val returnType: Int, val attributes: Int) { + class FunctionSymbolBase(val parameterTypes: IntArray, val returnType: Int, val attributes: Int, val escapes: Int?, val pointsTo: IntArray?) { - constructor(data: ArraySlice) : this(data.readIntArray(), data.readInt(), data.readInt()) + constructor(data: ArraySlice) : this(data.readIntArray(), data.readInt(), data.readInt(), data.readNullableInt(), data.readNullable { readIntArray() }) fun write(result: ArraySlice) { result.writeIntArray(parameterTypes) result.writeInt(returnType) result.writeInt(attributes) + result.writeNullableInt(escapes) + result.writeNullable(pointsTo) { writeIntArray(it) } } } - class ExternalFunctionSymbol(val hash: Long, val escapes: Int?, val pointsTo: IntArray?, val name: String?) { + class ExternalFunctionSymbol(val hash: Long, val name: String?) { - constructor(data: ArraySlice) : this(data.readLong(), data.readNullableInt(), - data.readNullable { readIntArray() }, data.readNullableString()) + constructor(data: ArraySlice) : this(data.readLong(), data.readNullableString()) fun write(result: ArraySlice) { result.writeLong(hash) - result.writeNullableInt(escapes) - result.writeNullable(pointsTo) { writeIntArray(it) } result.writeNullableString(name) } } @@ -376,8 +375,8 @@ internal object DFGSerializer { } companion object { - fun external(base: FunctionSymbolBase, hash: Long, escapes: Int?, pointsTo: IntArray?, name: String?) = - FunctionSymbol(base, ExternalFunctionSymbol(hash, escapes, pointsTo, name), null, null) + fun external(base: FunctionSymbolBase, hash: Long, name: String?) = + FunctionSymbol(base, ExternalFunctionSymbol(hash, name), null, null) fun public(base: FunctionSymbolBase, hash: Long, index: Int, bridgeTarget: Int?, name: String?) = FunctionSymbol(base, null, PublicFunctionSymbol(hash, index, bridgeTarget, name), null) @@ -807,15 +806,16 @@ internal object DFGSerializer { FunctionSymbolBase( symbol.parameterTypes.map { typeMap[it]!! }.toIntArray(), typeMap[symbol.returnType]!!, - symbol.attributes + symbol.attributes, + symbol.escapes, + symbol.pointsTo ) val symbol = it.key val bridgeTarget = (symbol as? DataFlowIR.FunctionSymbol.Declared)?.let { functionSymbolMap[it] } when (symbol) { is DataFlowIR.FunctionSymbol.External -> - FunctionSymbol.external(buildFunctionSymbolBase(symbol), symbol.hash, - symbol.escapes, symbol.pointsTo, symbol.name) + FunctionSymbol.external(buildFunctionSymbolBase(symbol), symbol.hash, symbol.name) is DataFlowIR.FunctionSymbol.Public -> FunctionSymbol.public(buildFunctionSymbolBase(symbol), symbol.hash, @@ -977,8 +977,7 @@ internal object DFGSerializer { val private = it.private when { external != null -> - DataFlowIR.FunctionSymbol.External(external.hash, - attributes, external.escapes, external.pointsTo, external.name) + DataFlowIR.FunctionSymbol.External(external.hash, attributes, external.name) public != null -> { val symbolTableIndex = public.index @@ -1000,6 +999,8 @@ internal object DFGSerializer { }.apply { parameterTypes = it.base.parameterTypes.map { types[it] }.toTypedArray() returnType = types[it.base.returnType] + escapes = it.base.escapes + pointsTo = it.base.pointsTo } } @@ -1110,7 +1111,7 @@ internal object DFGSerializer { NodeType.FIELD_READ -> { val fieldRead = it.fieldRead!! val receiver = fieldRead.receiver?.let { deserializeEdge(it) } - DataFlowIR.Node.FieldRead(receiver, deserializeField(fieldRead.field)) + DataFlowIR.Node.FieldRead(receiver, deserializeField(fieldRead.field), null) } NodeType.FIELD_WRITE -> { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt index ff5bd10d4c0..21b40efbf9f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression +import org.jetbrains.kotlin.ir.expressions.IrGetField import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature @@ -138,8 +139,10 @@ internal object DataFlowIR { val returnsUnit = attributes.and(FunctionAttributes.RETURNS_UNIT) != 0 val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0 - class External(val hash: Long, attributes: Int, - val escapes: Int?, val pointsTo: IntArray?, name: String? = null) + var escapes: Int? = null + var pointsTo: IntArray? = null + + class External(val hash: Long, attributes: Int, name: String? = null) : FunctionSymbol(attributes, name) { override fun equals(other: Any?): Boolean { @@ -180,7 +183,7 @@ internal object DataFlowIR { } override fun toString(): String { - return "PublicFunction(hash='$hash', symbolTableIndex='$symbolTableIndex', name='$name')" + return "PublicFunction(hash='$hash', name='$name', symbolTableIndex='$symbolTableIndex', escapes='$escapes', pointsTo='${pointsTo?.contentToString()})" } } @@ -200,7 +203,7 @@ internal object DataFlowIR { } override fun toString(): String { - return "PrivateFunction(index=$index, symbolTableIndex='$symbolTableIndex', name='$name')" + return "PrivateFunction(index=$index, symbolTableIndex='$symbolTableIndex', name='$name', escapes='$escapes', pointsTo='${pointsTo?.contentToString()})" } } } @@ -251,7 +254,7 @@ internal object DataFlowIR { class Singleton(val type: Type, val constructor: FunctionSymbol?) : Node() - class FieldRead(val receiver: Edge?, val field: Field) : Node() + class FieldRead(val receiver: Edge?, val field: Field, val ir: IrGetField?) : Node() class FieldWrite(val receiver: Edge?, val field: Field, val value: Edge) : Node() @@ -565,8 +568,10 @@ internal object DataFlowIR { val escapesBitMask = (escapesAnnotation?.allValueArguments?.get(escapesWhoDescriptor.name) as? ConstantValue)?.value @Suppress("UNCHECKED_CAST") val pointsToBitMask = (pointsToAnnotation?.allValueArguments?.get(pointsToOnWhomDescriptor.name) as? ConstantValue>)?.value - FunctionSymbol.External(name.localHash.value, attributes, escapesBitMask, - pointsToBitMask?.let { it.map { it.value }.toIntArray() }, takeName { name }) + FunctionSymbol.External(name.localHash.value, attributes, takeName { name }).apply { + escapes = escapesBitMask + pointsTo = pointsToBitMask?.let { it.map { it.value }.toIntArray() } + } } else -> { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt index 259d57765d1..764ff04f64c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt @@ -991,7 +991,8 @@ internal object Devirtualization { dfgSymbol = devirtualizedCallee, totalFunctions = devirtualizedCallee.module.numberOfFunctions, moduleDescriptor = devirtualizedCallee.module.descriptor, - functionIndex = devirtualizedCallee.symbolTableIndex + functionIndex = devirtualizedCallee.symbolTableIndex, + virtualCallee = callee ) fun IrBuilderWithScope.irDevirtualizedCall(callee: IrCall, actualType: KotlinType, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt index 4c1312c086c..e74f6d650f6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.backend.konan.DirectedGraphCondensationBuilder import org.jetbrains.kotlin.backend.konan.DirectedGraphMultiNode import org.jetbrains.kotlin.backend.konan.llvm.Lifetime import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.backend.konan.Context internal object EscapeAnalysis { @@ -92,10 +93,20 @@ internal object EscapeAnalysis { private class FunctionAnalysisResult(val function: DataFlowIR.Function, val nodesRoles: Map) - private class IntraproceduralAnalysis(val functions: Map, + private class IntraproceduralAnalysis(val context: Context, + val moduleDFG: ModuleDFG, val externalModulesDFG: ExternalModulesDFG, val callGraph: CallGraph) { + val functions = moduleDFG.functions// TODO: use for cross-module analysis: + externalModulesDFG.functionDFGs + + 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") + } + fun analyze(): Map { + val nothing = moduleDFG.symbolTable.mapClass(context.ir.symbols.nothing.owner).resolved() return callGraph.nodes.associateBy({ it.symbol }) { val function = functions[it.symbol]!! val body = function.body @@ -124,7 +135,9 @@ internal object EscapeAnalysis { } is DataFlowIR.Node.Singleton -> { - assignRole(node, Role.WRITTEN_TO_GLOBAL, null /* TODO */) + val type = node.type.resolved() + if (type != nothing) + assignRole(node, Role.WRITTEN_TO_GLOBAL, null /* TODO */) } is DataFlowIR.Node.FieldRead -> { @@ -154,13 +167,22 @@ internal object EscapeAnalysis { } } + is DataFlowIR.Node.ArrayWrite -> { + assignRole(node.array.node, Role.FIELD_WRITTEN, RoleInfoEntry(node.value.node)) + assignRole(node.value.node, Role.FIELD_WRITTEN, RoleInfoEntry(node.array.node)) + } + + is DataFlowIR.Node.ArrayRead -> { + assignRole(node.array.node, Role.FIELD_WRITTEN, RoleInfoEntry(node)) + assignRole(node, Role.FIELD_WRITTEN, RoleInfoEntry(node.array.node)) + } + is DataFlowIR.Node.Variable -> { for (value in node.values) { assignRole(node, Role.FIELD_WRITTEN, RoleInfoEntry(value.node)) assignRole(value.node, Role.FIELD_WRITTEN, RoleInfoEntry(node)) } } - else -> TODO() } } FunctionAnalysisResult(function, nodesRoles) @@ -228,6 +250,7 @@ internal object EscapeAnalysis { private class InterproceduralAnalysis(val callGraph: CallGraph, val intraproceduralAnalysisResult: Map, + val externalModulesDFG: ExternalModulesDFG, val lifetimes: MutableMap) { val escapeAnalysisResults = mutableMapOf() @@ -238,8 +261,12 @@ internal object EscapeAnalysis { callGraph.directEdges.forEach { t, u -> println(" FUN $t") u.callSites.forEach { - val local = callGraph.directEdges.containsKey(it.actualCallee) - println(" CALLS ${if (local) "LOCAL" else "EXTERNAL"} ${it.actualCallee}") + val label = when { + it.isVirtual -> "VIRTUAL" + callGraph.directEdges.containsKey(it.actualCallee) -> "LOCAL" + else -> "EXTERNAL" + } + println(" CALLS $label ${it.actualCallee}") } callGraph.reversedEdges[t]!!.forEach { println(" CALLED BY $it") @@ -318,10 +345,31 @@ internal object EscapeAnalysis { } } } + multiNode.nodes.forEach { + val escapeAnalysisResult = escapeAnalysisResults[it]!! + var escapes = 0 + val pointsTo = escapeAnalysisResult.parameters.withIndex().map { (index, parameterEAResult) -> + if (parameterEAResult.escapes) + escapes = escapes or (1 shl index) + var pointsToMask = 0 + parameterEAResult.pointsTo.forEach { + pointsToMask = pointsToMask or (1 shl it) + } + pointsToMask + }.toIntArray() + it.escapes = escapes + it.pointsTo = pointsTo + } for (graph in pointsToGraphs.values) { - graph.nodes.keys - .filterIsInstance() - .forEach { call -> call.irCallSite?.let { lifetimes.put(it, graph.lifetimeOf(call)) } } + for (node in graph.nodes.keys) { + val ir = when (node) { + is DataFlowIR.Node.Call -> node.irCallSite + is DataFlowIR.Node.ArrayRead -> node.irCallSite + is DataFlowIR.Node.FieldRead -> node.ir + else -> null + } + ir?.let { lifetimes.put(it, graph.lifetimeOf(node)) } + } } } @@ -333,9 +381,12 @@ internal object EscapeAnalysis { callGraph.directEdges[function]!!.callSites.forEach { val callee = it.actualCallee - val calleeEAResult = callGraph.directEdges[callee]?.let { escapeAnalysisResults[it.symbol]!! } - ?: getExternalFunctionEAResult(it) - pointsToGraph.processCall(it.call, calleeEAResult) + val calleeEAResult = if (it.isVirtual) + getExternalFunctionEAResult(it) + else + callGraph.directEdges[callee]?.let { escapeAnalysisResults[it.symbol]!! } + ?: getExternalFunctionEAResult(it) + pointsToGraph.processCall(it, calleeEAResult) } DEBUG_OUTPUT(0) { @@ -358,23 +409,29 @@ internal object EscapeAnalysis { val numberOfParameters = symbol.parameterTypes.size return FunctionEscapeAnalysisResult((0..numberOfParameters).map { ParameterEscapeAnalysisResult( - escapes = true, + escapes = true, pointsTo = IntArray(0) ) }.toTypedArray()) } - private fun getExternalFunctionEAResult(callSite: CallGraphNode.CallSite): FunctionEscapeAnalysisResult { - val callee = callSite.actualCallee + private fun DataFlowIR.FunctionSymbol.resolved(): DataFlowIR.FunctionSymbol { + if (this is DataFlowIR.FunctionSymbol.External) + return externalModulesDFG.publicFunctions[this.hash] ?: this + return this + } - val calleeEAResult = if (callSite.call is DataFlowIR.Node.VirtualCall) { + private fun getExternalFunctionEAResult(callSite: CallGraphNode.CallSite): FunctionEscapeAnalysisResult { + val callee = callSite.actualCallee.resolved() + + val calleeEAResult = if (callSite.isVirtual) { DEBUG_OUTPUT(0) { println("A virtual call: $callee") } getConservativeFunctionEAResult(callee) } else { - callSite.call as DataFlowIR.Node.StaticCall - callee as DataFlowIR.FunctionSymbol.External + + DEBUG_OUTPUT(0) { println("An external call: $callee") } FunctionEscapeAnalysisResult.fromBits( callee.escapes ?: 0, @@ -408,14 +465,11 @@ internal object EscapeAnalysis { val beingReturned = roles.has(Role.RETURN_VALUE) - var parameterPointingOnUs: Int? = null - var pointsMoreThanOneParameter = false + val parametersPointingOnUs = mutableSetOf() fun addIncomingParameter(parameter: Int) { - if (pointsMoreThanOneParameter) return - if (parameterPointingOnUs == null) - parameterPointingOnUs = parameter - else pointsMoreThanOneParameter = true + if (kind == PointsToGraphNodeKind.ESCAPES) return + parametersPointingOnUs += parameter } } @@ -431,25 +485,28 @@ internal object EscapeAnalysis { PointsToGraphNodeKind.ESCAPES -> Lifetime.GLOBAL PointsToGraphNodeKind.LOCAL -> { - if (it.pointsMoreThanOneParameter) - Lifetime.GLOBAL - else { - val parameterPointingOnUs = it.parameterPointingOnUs - if (parameterPointingOnUs != null) + if (it.parametersPointingOnUs.isEmpty()) { + // A value is neither stored into a global nor into any parameter nor into the return value - + // it can be allocated locally. + Lifetime.LOCAL + } else { + if (it.parametersPointingOnUs.size == 1) { // TODO: remove. // A value is stored into a parameter field. - Lifetime.PARAMETER_FIELD(parameterPointingOnUs) - else - // A value is neither stored into a global nor into any parameter nor into the return value - - // it can be allocated locally. - Lifetime.LOCAL + Lifetime.PARAMETER_FIELD(it.parametersPointingOnUs.first()) + } else { + // A value is stored into several parameters fields. + Lifetime.PARAMETERS_FIELD(it.parametersPointingOnUs.toIntArray(), false) + } } } PointsToGraphNodeKind.RETURN_VALUE -> { when { - it.parameterPointingOnUs != null -> Lifetime.GLOBAL // If a value is explicitly returned. returnValues.contains(node) -> Lifetime.RETURN_VALUE + + it.parametersPointingOnUs.isNotEmpty() -> Lifetime.PARAMETERS_FIELD(it.parametersPointingOnUs.toIntArray(), true) + // A value is stored into a field of the return value. else -> Lifetime.INDIRECT_RETURN_VALUE } @@ -515,25 +572,38 @@ internal object EscapeAnalysis { } } - fun processCall(callSite: DataFlowIR.Node.Call, calleeEscapeAnalysisResult: FunctionEscapeAnalysisResult) { + fun print_digraph() { + println("digraph {") + val ids = ids ?: functionAnalysisResult.function.body.nodes.withIndex().associateBy({ it.value }, { it.index }) + nodes.forEach { t, u -> + u.edges.forEach { + println(" ${ids[t]} -> ${ids[it]};") + } + } + println("}") + } + + fun processCall(callSite: CallGraphNode.CallSite, calleeEscapeAnalysisResult: FunctionEscapeAnalysisResult) { + val call = callSite.call DEBUG_OUTPUT(0) { println("Processing callSite") - println(nodeToStringWhole(callSite)) + println(nodeToStringWhole(call)) + println("Actual callee: ${callSite.actualCallee}") println("Callee escape analysis result:") println(calleeEscapeAnalysisResult.toString()) } - val arguments = if (callSite is DataFlowIR.Node.NewObject) { - (0..callSite.arguments.size).map { - if (it == 0) callSite else callSite.arguments[it - 1].node + val arguments = if (call is DataFlowIR.Node.NewObject) { + (0..call.arguments.size).map { + if (it == 0) call else call.arguments[it - 1].node } } else { - (0..callSite.arguments.size).map { - if (it < callSite.arguments.size) callSite.arguments[it].node else callSite + (0..call.arguments.size).map { + if (it < call.arguments.size) call.arguments[it].node else call } } - for (index in 0..callSite.arguments.size) { + for (index in 0..call.arguments.size) { val parameterEAResult = calleeEscapeAnalysisResult.parameters[index] val from = arguments[index] if (parameterEAResult.escapes) { @@ -660,12 +730,12 @@ internal object EscapeAnalysis { } } - fun computeLifetimes(moduleDFG: ModuleDFG, externalModulesDFG: ExternalModulesDFG, + fun computeLifetimes(context: Context, moduleDFG: ModuleDFG, externalModulesDFG: ExternalModulesDFG, callGraph: CallGraph, lifetimes: MutableMap) { assert(lifetimes.isEmpty()) val intraproceduralAnalysisResult = - IntraproceduralAnalysis(moduleDFG.functions + externalModulesDFG.functionDFGs, callGraph).analyze() - InterproceduralAnalysis(callGraph, intraproceduralAnalysisResult, lifetimes).analyze() + IntraproceduralAnalysis(context, moduleDFG, externalModulesDFG, callGraph).analyze() + InterproceduralAnalysis(callGraph, intraproceduralAnalysisResult, externalModulesDFG, lifetimes).analyze() } } \ No newline at end of file