[K/N][optmz] Another way of resolving escape analysis divergence
The idea is the following: if the analysis for some strongly connected component seems not to be converging, reanalyze it again considering all backward edges (i.e. leading to cycles) pessimistic, basically treating the corresponding calls as external ones. This helps to have more reasonable results for not-converging components still leaving the chance for them to converge optimistically first.
This commit is contained in:
+108
-35
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.backend.konan.optimizations
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.atMostOne
|
||||
import org.jetbrains.kotlin.backend.common.peek
|
||||
import org.jetbrains.kotlin.backend.common.pop
|
||||
import org.jetbrains.kotlin.backend.common.push
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
@@ -143,6 +144,7 @@ internal object EscapeAnalysis {
|
||||
val ROOT_SCOPE = 0
|
||||
val RETURN_VALUE = -1
|
||||
val PARAMETER = -2
|
||||
val GLOBAL = -3
|
||||
}
|
||||
|
||||
object DivergenceResolutionParams {
|
||||
@@ -545,6 +547,12 @@ internal object EscapeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
private enum class ComputationState {
|
||||
NEW,
|
||||
PENDING,
|
||||
DONE
|
||||
}
|
||||
|
||||
private class Stats {
|
||||
var globalAllocsCount = 0
|
||||
var stackAllocsCount = 0
|
||||
@@ -555,6 +563,14 @@ internal object EscapeAnalysis {
|
||||
|
||||
private val stats = Stats()
|
||||
|
||||
private fun maxPointsToGraphSizeOf(function: DataFlowIR.FunctionSymbol) = with(DivergenceResolutionParams) {
|
||||
// A heuristic: the majority of functions have their points-to graph size linear in number of IR (or DFG) nodes,
|
||||
// there are exceptions but it's a trade-off we have to make.
|
||||
// The trick with [NegligibleSize] handles functions that basically delegate their work to other functions.
|
||||
val numberOfNodes = intraproceduralAnalysisResults[function]!!.function.body.allScopes.sumOf { it.nodes.size }
|
||||
NegligibleSize + numberOfNodes * SwellingFactor
|
||||
}
|
||||
|
||||
private fun analyze(callGraph: CallGraph, multiNode: DirectedGraphMultiNode<DataFlowIR.FunctionSymbol.Declared>) {
|
||||
val nodes = multiNode.nodes.filter { intraproceduralAnalysisResults.containsKey(it) }.toMutableSet()
|
||||
|
||||
@@ -571,11 +587,12 @@ internal object EscapeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
val pointsToGraphs = mutableMapOf<DataFlowIR.FunctionSymbol.Declared, PointsToGraph>()
|
||||
var pointsToGraphs = mutableMapOf<DataFlowIR.FunctionSymbol.Declared, PointsToGraph>()
|
||||
var failedToConverge = false
|
||||
val toAnalyze = mutableSetOf<DataFlowIR.FunctionSymbol.Declared>()
|
||||
toAnalyze.addAll(nodes)
|
||||
val numberOfRuns = nodes.associateWith { 0 }.toMutableMap()
|
||||
while (toAnalyze.isNotEmpty()) {
|
||||
while (!failedToConverge && toAnalyze.isNotEmpty()) {
|
||||
val function = toAnalyze.first()
|
||||
toAnalyze.remove(function)
|
||||
numberOfRuns[function] = numberOfRuns[function]!! + 1
|
||||
@@ -587,44 +604,31 @@ internal object EscapeAnalysis {
|
||||
val pointsToGraph = PointsToGraph(function)
|
||||
pointsToGraphs[function] = pointsToGraph
|
||||
|
||||
val maxAllowedGraphSize = with(DivergenceResolutionParams) {
|
||||
// A heuristic: the majority of functions have their points-to graph size linear in number of IR (or DFG) nodes,
|
||||
// there are exceptions but it's a trade-off we have to make.
|
||||
// The trick with [NegligibleSize] handles functions that basically delegate their work to other functions.
|
||||
val numberOfNodes = intraproceduralAnalysisResults[function]!!.function.body.allScopes.sumOf { it.nodes.size }
|
||||
NegligibleSize + numberOfNodes * SwellingFactor
|
||||
}
|
||||
|
||||
var analyzedSuccessfully = analyze(callGraph, pointsToGraph, function, maxAllowedGraphSize)
|
||||
var processCallers = true
|
||||
if (analyzedSuccessfully) {
|
||||
if (!analyze(callGraph.directEdges[function]!!.callSites, pointsToGraph, function, maxPointsToGraphSizeOf(function))) {
|
||||
failedToConverge = true
|
||||
} else {
|
||||
val endResult = escapeAnalysisResults[function]!!
|
||||
if (startResult == endResult) {
|
||||
context.log { "Escape analysis is not changed" }
|
||||
processCallers = false
|
||||
} else {
|
||||
context.log { "Escape analysis was refined:\n$endResult" }
|
||||
if (numberOfRuns[function]!! > DivergenceResolutionParams.MaxAttempts)
|
||||
analyzedSuccessfully = false
|
||||
failedToConverge = true
|
||||
else {
|
||||
callGraph.reversedEdges[function]?.forEach {
|
||||
if (nodes.contains(it))
|
||||
toAnalyze.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!analyzedSuccessfully) {
|
||||
// TODO: suboptimal. May be it is possible somehow handle the entire component at once?
|
||||
context.log {
|
||||
"WARNING: Escape analysis for $function seems not to be converging." +
|
||||
" Assuming conservative results."
|
||||
}
|
||||
escapeAnalysisResults[function] = FunctionEscapeAnalysisResult.pessimistic(function.parameters.size)
|
||||
nodes.remove(function)
|
||||
}
|
||||
if (failedToConverge)
|
||||
context.log { "WARNING: Escape analysis for $function seems not to be converging. Falling back to conservative strategy." }
|
||||
}
|
||||
|
||||
if (processCallers) {
|
||||
callGraph.reversedEdges[function]?.forEach {
|
||||
if (nodes.contains(it))
|
||||
toAnalyze.add(it)
|
||||
}
|
||||
}
|
||||
if (failedToConverge) {
|
||||
pointsToGraphs = analyzeComponentPessimistically(callGraph, multiNode)
|
||||
}
|
||||
|
||||
pointsToGraphs.forEach { (function, graph) ->
|
||||
@@ -634,8 +638,6 @@ internal object EscapeAnalysis {
|
||||
stats.totalPTGSize += graph.allNodes.size
|
||||
stats.totalDFGSize += intraproceduralAnalysisResults[function]!!.function.body.allScopes.sumOf { it.nodes.size }
|
||||
|
||||
// TODO: suboptimal.
|
||||
if (function !in nodes) return@forEach
|
||||
for (node in graph.nodes.keys) {
|
||||
node.ir?.let {
|
||||
val lifetime = graph.lifetimeOf(node)
|
||||
@@ -653,6 +655,73 @@ internal object EscapeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeComponentPessimistically(
|
||||
callGraph: CallGraph,
|
||||
multiNode: DirectedGraphMultiNode<DataFlowIR.FunctionSymbol.Declared>
|
||||
): MutableMap<DataFlowIR.FunctionSymbol.Declared, PointsToGraph> {
|
||||
val nodes = multiNode.nodes.filter { intraproceduralAnalysisResults.containsKey(it) }
|
||||
val pointsToGraphs = mutableMapOf<DataFlowIR.FunctionSymbol.Declared, PointsToGraph>()
|
||||
val computationStates = mutableMapOf<DataFlowIR.FunctionSymbol.Declared, ComputationState>()
|
||||
nodes.forEach { computationStates[it] = ComputationState.NEW }
|
||||
val toAnalyze = nodes.toMutableList()
|
||||
while (toAnalyze.isNotEmpty()) {
|
||||
val function = toAnalyze.peek()!!
|
||||
val state = computationStates[function]!!
|
||||
val callSites = callGraph.directEdges[function]!!.callSites
|
||||
when (state) {
|
||||
ComputationState.NEW -> {
|
||||
computationStates[function] = ComputationState.PENDING
|
||||
for (callSite in callSites) {
|
||||
val callee = callSite.actualCallee
|
||||
val calleeComputationState = computationStates[callee]
|
||||
if (callSite.isVirtual
|
||||
|| callee !is DataFlowIR.FunctionSymbol.Declared // An external call.
|
||||
|| calleeComputationState == null // A call to a function from other component.
|
||||
|| calleeComputationState == ComputationState.DONE // Already analyzed.
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (calleeComputationState == ComputationState.PENDING) {
|
||||
// A cycle - break it by assuming nothing about the callee.
|
||||
// This is not the callee's final result - it will be recomputed later in the loop.
|
||||
escapeAnalysisResults[callee] = FunctionEscapeAnalysisResult.pessimistic(callee.parameters.size)
|
||||
} else {
|
||||
computationStates[callee] = ComputationState.NEW
|
||||
toAnalyze.push(callee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ComputationState.PENDING -> {
|
||||
toAnalyze.pop()
|
||||
computationStates[function] = ComputationState.DONE
|
||||
val pointsToGraph = PointsToGraph(function)
|
||||
if (analyze(callSites, pointsToGraph, function, maxPointsToGraphSizeOf(function)))
|
||||
pointsToGraphs[function] = pointsToGraph
|
||||
else {
|
||||
// TODO: suboptimal. May be it is possible somehow handle the entire component at once?
|
||||
context.log {
|
||||
"WARNING: Escape analysis for $function seems not to be converging." +
|
||||
" Assuming conservative results."
|
||||
}
|
||||
escapeAnalysisResults[function] = FunctionEscapeAnalysisResult.pessimistic(function.parameters.size)
|
||||
// Invalidate the points-to graph.
|
||||
pointsToGraphs[function] = PointsToGraph(function).apply {
|
||||
allNodes.forEach { it.depth = Depths.GLOBAL }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ComputationState.DONE -> {
|
||||
toAnalyze.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pointsToGraphs
|
||||
}
|
||||
|
||||
private fun arrayLengthOf(node: DataFlowIR.Node): Int? =
|
||||
(node as? DataFlowIR.Node.SimpleConst<*>)?.value as? Int
|
||||
// In case of several possible values, it's unknown what is used.
|
||||
@@ -679,7 +748,7 @@ internal object EscapeAnalysis {
|
||||
pointerSize /* typeinfo */ + 4 /* size */ + itemSize * length.toLong()
|
||||
|
||||
private fun analyze(
|
||||
callGraph: CallGraph,
|
||||
callSites: List<CallGraphNode.CallSite>,
|
||||
pointsToGraph: PointsToGraph,
|
||||
function: DataFlowIR.FunctionSymbol.Declared,
|
||||
maxAllowedGraphSize: Int
|
||||
@@ -688,7 +757,7 @@ internal object EscapeAnalysis {
|
||||
pointsToGraph.log()
|
||||
pointsToGraph.logDigraph(false)
|
||||
|
||||
callGraph.directEdges[function]!!.callSites.forEach {
|
||||
callSites.forEach {
|
||||
val callee = it.actualCallee
|
||||
val calleeEAResult = if (it.isVirtual)
|
||||
getExternalFunctionEAResult(it)
|
||||
@@ -757,7 +826,8 @@ internal object EscapeAnalysis {
|
||||
STACK,
|
||||
LOCAL,
|
||||
PARAMETER,
|
||||
RETURN_VALUE
|
||||
RETURN_VALUE,
|
||||
GLOBAL
|
||||
}
|
||||
|
||||
private sealed class PointsToGraphEdge(val node: PointsToGraphNode) {
|
||||
@@ -787,6 +857,7 @@ internal object EscapeAnalysis {
|
||||
}
|
||||
|
||||
val kind get() = when {
|
||||
depth == Depths.GLOBAL -> PointsToGraphNodeKind.GLOBAL
|
||||
depth == Depths.PARAMETER -> PointsToGraphNodeKind.PARAMETER
|
||||
depth == Depths.RETURN_VALUE -> PointsToGraphNodeKind.RETURN_VALUE
|
||||
depth != nodeInfo.depth -> PointsToGraphNodeKind.LOCAL
|
||||
@@ -858,6 +929,8 @@ internal object EscapeAnalysis {
|
||||
if (escapes(node))
|
||||
Lifetime.GLOBAL
|
||||
else when (node.kind) {
|
||||
PointsToGraphNodeKind.GLOBAL -> Lifetime.GLOBAL
|
||||
|
||||
PointsToGraphNodeKind.PARAMETER -> Lifetime.ARGUMENT
|
||||
|
||||
PointsToGraphNodeKind.STACK -> {
|
||||
|
||||
Reference in New Issue
Block a user