FIR CFA: remove CFGNode.outgoingEdges
If it's symmetric with incomingEdges, then what's the point?..
This commit is contained in:
+2
-2
@@ -72,8 +72,8 @@ private fun <I> ControlFlowGraph.collectDataForNodeInternal(
|
||||
val previousData =
|
||||
previousNodes.mapNotNull {
|
||||
val k = when (direction) {
|
||||
TraverseDirection.Forward -> node.incomingEdges[it]?.label ?: NormalPath
|
||||
TraverseDirection.Backward -> node.outgoingEdges[it]?.label ?: NormalPath
|
||||
TraverseDirection.Forward -> node.edgeFrom(it).label
|
||||
TraverseDirection.Backward -> node.edgeTo(it).label
|
||||
}
|
||||
val v = nodeMap[it] ?: return@mapNotNull null
|
||||
k to v
|
||||
|
||||
@@ -26,7 +26,7 @@ fun CFGNode<*>.isEnterNode(direction: TraverseDirection): Boolean = when (direct
|
||||
|
||||
val CFGNode<*>.previousCfgNodes: List<CFGNode<*>>
|
||||
get() = previousNodes.filter {
|
||||
val kind = incomingEdges.getValue(it).kind
|
||||
val kind = edgeFrom(it).kind
|
||||
if (this.isDead) {
|
||||
kind.usedInCfa
|
||||
} else {
|
||||
@@ -39,7 +39,7 @@ val CFGNode<*>.followingCfgNodes: List<CFGNode<*>>
|
||||
val nodes = mutableListOf<CFGNode<*>>()
|
||||
|
||||
followingNodes.filterTo(nodes) {
|
||||
val kind = outgoingEdges.getValue(it).kind
|
||||
val kind = edgeTo(it).kind
|
||||
kind.usedInCfa && !kind.isDead
|
||||
}
|
||||
(this as? CFGNodeWithSubgraphs<*>)?.subGraphs?.mapTo(nodes) { it.enterNode }
|
||||
|
||||
+2
-6
@@ -232,7 +232,7 @@ object FirMemberPropertiesChecker : FirClassChecker() {
|
||||
else -> null
|
||||
}
|
||||
val cfg = controlFlowGraphReference?.controlFlowGraph ?: return emptySet()
|
||||
return cfg.exitNode.incomingEdges.keys
|
||||
return cfg.exitNode.previousNodes
|
||||
.map { it.fir }
|
||||
.filter { it.isDeadEnd() }
|
||||
.toSet()
|
||||
@@ -246,10 +246,6 @@ object FirMemberPropertiesChecker : FirClassChecker() {
|
||||
*/
|
||||
private fun FirElement.isDeadEnd(): Boolean {
|
||||
val cfg = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return false
|
||||
cfg.exitNode.incomingEdges.keys.find { it is BlockExitNode }?.let {
|
||||
return it.isDead
|
||||
}
|
||||
|
||||
return cfg.exitNode.incomingEdges.keys.any { it.isDead }
|
||||
return cfg.exitNode.previousNodes.find { it is BlockExitNode }?.isDead ?: cfg.exitNode.previousNodes.any { it.isDead }
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ object FirTailrecFunctionChecker : FirSimpleFunctionChecker() {
|
||||
|
||||
private fun CFGNode<*>.hasMoreFollowingInstructions(tailrecFunction: FirSimpleFunction): Boolean {
|
||||
for (next in followingNodes) {
|
||||
val edge = outgoingEdges.getValue(next)
|
||||
val edge = edgeTo(next)
|
||||
if (!edge.kind.usedInCfa || edge.kind.isDead) continue
|
||||
if (edge.kind.isBack) return true
|
||||
val hasMore = when (next) {
|
||||
|
||||
+1
-1
@@ -1128,7 +1128,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
// In that case `mergeIncomingFlow` will automatically ensure consistency once called on that node.
|
||||
private fun CFGNode<*>.buildIncomingFlow(): MutableFlow {
|
||||
val previousFlows = previousNodes.mapNotNull {
|
||||
val incomingEdgeKind = incomingEdges.getValue(it).kind
|
||||
val incomingEdgeKind = edgeFrom(it).kind
|
||||
if (if (isDead) !incomingEdgeKind.usedInDeadDfa else !incomingEdgeKind.usedInDfa) return@mapNotNull null
|
||||
// `MergePostponedLambdaExitsNode` nodes form a parallel data flow graph. We never compute
|
||||
// data flow for any of them until reaching a completed call.
|
||||
|
||||
+4
-4
@@ -114,7 +114,7 @@ class ControlFlowGraphBuilder {
|
||||
}
|
||||
|
||||
val returnValues = exitNode.previousNodes.mapNotNullTo(mutableSetOf()) {
|
||||
val edge = exitNode.incomingEdges.getValue(it)
|
||||
val edge = exitNode.edgeFrom(it)
|
||||
// * NormalPath: last expression = return value
|
||||
// * UncaughtExceptionPath: last expression = whatever threw, *not* a return value
|
||||
// * ReturnPath(this lambda): these go from `finally` blocks, so that's not the return value;
|
||||
@@ -1009,7 +1009,7 @@ class ControlFlowGraphBuilder {
|
||||
val exitNode = createFinallyBlockExitNode(enterNode.fir)
|
||||
popAndAddEdge(exitNode)
|
||||
val allNormalInputsAreDead = enterNode.previousNodes.all {
|
||||
val edge = enterNode.incomingEdges.getValue(it)
|
||||
val edge = enterNode.edgeFrom(it)
|
||||
edge.kind.isDead || edge.label != NormalPath
|
||||
}
|
||||
addEdge(exitNode, tryExitNode, isDead = allNormalInputsAreDead)
|
||||
@@ -1087,7 +1087,7 @@ class ControlFlowGraphBuilder {
|
||||
private fun completeFunctionCall(node: FunctionCallNode) {
|
||||
if (!node.fir.resultType.isNothing) return
|
||||
val stub = withLevelOfNode(node) { createStubNode() }
|
||||
val edges = node.followingNodes.map { it to node.outgoingEdges.getValue(it) }
|
||||
val edges = node.followingNodes.map { it to node.edgeTo(it) }
|
||||
CFGNode.removeAllOutgoingEdges(node)
|
||||
CFGNode.addEdge(node, stub, EdgeKind.DeadForward, propagateDeadness = false)
|
||||
for ((to, edge) in edges) {
|
||||
@@ -1448,7 +1448,7 @@ class ControlFlowGraphBuilder {
|
||||
private fun propagateDeadnessForward(node: CFGNode<*>) {
|
||||
if (!node.isDead) return
|
||||
for (next in node.followingNodes) {
|
||||
val kind = node.outgoingEdges.getValue(next).kind
|
||||
val kind = node.edgeTo(next).kind
|
||||
if (CFGNode.killEdge(node, next, propagateDeadness = false) && !kind.isBack && kind.usedInCfa) {
|
||||
next.updateDeadStatus()
|
||||
propagateDeadnessForward(next)
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ class FirControlFlowGraphRenderVisitor(
|
||||
|
||||
private fun Printer.renderEdges(graph: ControlFlowGraph) {
|
||||
for (node in graph.nodes) {
|
||||
for ((style, group) in node.followingNodes.groupBy { node.outgoingEdges.getValue(it).style }.entries.sortedBy { it.key }) {
|
||||
for ((style, group) in node.followingNodes.groupBy { node.edgeTo(it).style }.entries.sortedBy { it.key }) {
|
||||
val mappedGroup = group.map { indices.getValue(it) }.sorted()
|
||||
print(indices.getValue(node), EDGE, mappedGroup.joinToString(prefix = "{", postfix = "}", separator = " "))
|
||||
style?.let { printWithNoIndent(" $it") }
|
||||
|
||||
@@ -36,9 +36,7 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
from._followingNodes += to
|
||||
to._previousNodes += from
|
||||
if (kind != EdgeKind.Forward || label != NormalPath) {
|
||||
val edge = Edge.create(label, kind)
|
||||
from.insertOutgoingEdge(to, edge)
|
||||
to.insertIncomingEdge(from, edge)
|
||||
to.insertIncomingEdge(from, Edge.create(label, kind))
|
||||
}
|
||||
if (propagateDeadness && kind == EdgeKind.DeadForward) {
|
||||
to.isDead = true
|
||||
@@ -47,11 +45,9 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
|
||||
@CfgInternals
|
||||
fun killEdge(from: CFGNode<*>, to: CFGNode<*>, propagateDeadness: Boolean): Boolean {
|
||||
val oldEdge = to.incomingEdges.getValue(from)
|
||||
val oldEdge = to.edgeFrom(from)
|
||||
if (oldEdge.kind.isDead) return false
|
||||
|
||||
val newEdge = Edge.create(oldEdge.label, if (oldEdge.kind.isBack) EdgeKind.DeadBackward else EdgeKind.DeadForward)
|
||||
from.insertOutgoingEdge(to, newEdge)
|
||||
to.insertIncomingEdge(from, newEdge)
|
||||
if (propagateDeadness) {
|
||||
to.isDead = true
|
||||
@@ -63,7 +59,6 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
fun removeAllIncomingEdges(to: CFGNode<*>) {
|
||||
for (from in to._previousNodes) {
|
||||
from._followingNodes.remove(to)
|
||||
from._outgoingEdges?.remove(to)
|
||||
}
|
||||
to._incomingEdges = null
|
||||
to._previousNodes.clear()
|
||||
@@ -75,12 +70,8 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
to._previousNodes.remove(from)
|
||||
to._incomingEdges?.remove(from)
|
||||
}
|
||||
from._outgoingEdges = null
|
||||
from._followingNodes.clear()
|
||||
}
|
||||
|
||||
private fun createEmptyEdgeMapWithDefault(): MutableMap<CFGNode<*>, Edge> =
|
||||
mutableMapOf<CFGNode<*>, Edge>().withDefault { Edge.Normal_Forward }
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -95,33 +86,18 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
val followingNodes: List<CFGNode<*>> get() = _followingNodes
|
||||
|
||||
private var _incomingEdges: MutableMap<CFGNode<*>, Edge>? = null
|
||||
private var _outgoingEdges: MutableMap<CFGNode<*>, Edge>? = null
|
||||
|
||||
private fun insertIncomingEdge(to: CFGNode<*>, edge: Edge) {
|
||||
if (_incomingEdges == null) {
|
||||
_incomingEdges = createEmptyEdgeMapWithDefault()
|
||||
private fun insertIncomingEdge(from: CFGNode<*>, edge: Edge) {
|
||||
val map = _incomingEdges
|
||||
if (map != null) {
|
||||
map[from] = edge
|
||||
} else {
|
||||
_incomingEdges = mutableMapOf(from to edge)
|
||||
}
|
||||
_incomingEdges?.put(to, edge)
|
||||
}
|
||||
|
||||
private fun insertOutgoingEdge(to: CFGNode<*>, edge: Edge) {
|
||||
if (_outgoingEdges == null) {
|
||||
_outgoingEdges = createEmptyEdgeMapWithDefault()
|
||||
}
|
||||
_outgoingEdges?.put(to, edge)
|
||||
}
|
||||
|
||||
// Note: may be it should be made MapWithImplicitDefault and override getOrImplicitDefault and not get
|
||||
// However, it works even in the current form
|
||||
private object EmptyEdgesMap : AbstractMap<CFGNode<*>, Edge>() {
|
||||
override val entries: Set<Map.Entry<CFGNode<*>, Edge>>
|
||||
get() = emptySet()
|
||||
|
||||
override fun get(key: CFGNode<*>): Edge = Edge.Normal_Forward
|
||||
}
|
||||
|
||||
val incomingEdges: Map<CFGNode<*>, Edge> get() = _incomingEdges ?: EmptyEdgesMap
|
||||
val outgoingEdges: Map<CFGNode<*>, Edge> get() = _outgoingEdges ?: EmptyEdgesMap
|
||||
fun edgeFrom(other: CFGNode<*>) = _incomingEdges?.get(other) ?: Edge.Normal_Forward
|
||||
fun edgeTo(other: CFGNode<*>) = other.edgeFrom(this)
|
||||
|
||||
abstract val fir: E
|
||||
var isDead: Boolean = false
|
||||
@@ -139,13 +115,9 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
@CfgInternals
|
||||
fun updateDeadStatus() {
|
||||
isDead = if (this is UnionNodeMarker)
|
||||
incomingEdges.values.any {
|
||||
it.kind == EdgeKind.DeadForward
|
||||
}
|
||||
_incomingEdges?.let { map -> map.values.any { it.kind.isDead } } == true
|
||||
else
|
||||
incomingEdges.size == previousNodes.size && incomingEdges.values.all {
|
||||
it.kind == EdgeKind.DeadForward || !it.kind.usedInCfa
|
||||
}
|
||||
_incomingEdges?.let { map -> map.size == previousNodes.size && map.values.all { it.kind.isDead || !it.kind.usedInCfa } } == true
|
||||
}
|
||||
|
||||
abstract fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ private fun ControlFlowGraph.orderNodes(): LinkedHashSet<CFGNode<*>> {
|
||||
while (stack.isNotEmpty()) {
|
||||
val node = stack.removeFirst()
|
||||
val previousNodes = node.previousNodes
|
||||
if (previousNodes.any { it !in visitedNodes && it.owner == this && !node.incomingEdges.getValue(it).kind.isBack }) {
|
||||
if (previousNodes.any { it !in visitedNodes && it.owner == this && !node.edgeFrom(it).kind.isBack }) {
|
||||
stack.addLast(node)
|
||||
continue
|
||||
}
|
||||
|
||||
+9
-20
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.FirControlFlowGraphReferenceImpl
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeKind
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||
import org.jetbrains.kotlin.test.Assertions
|
||||
|
||||
@@ -27,28 +26,18 @@ class FirCfgConsistencyChecker(private val assertions: Assertions) : FirVisitorV
|
||||
|
||||
private fun checkConsistency(graph: ControlFlowGraph) {
|
||||
for (node in graph.nodes) {
|
||||
assertions.assertEquals(node.followingNodes.size, node.followingNodes.toSet().size) { "followingNodes has repeats: $node" }
|
||||
assertions.assertEquals(node.previousNodes.size, node.previousNodes.toSet().size) { "previousNodes has repeats: $node" }
|
||||
for (to in node.followingNodes) {
|
||||
checkEdge(node, to)
|
||||
assertions.assertContainsElements(to.previousNodes, node)
|
||||
assertions.assertFalse(node.isDead && to.isDead && to.edgeFrom(node).kind.usedInDfa) {
|
||||
"data flow between dead nodes: $node -> $to"
|
||||
}
|
||||
}
|
||||
for (from in node.previousNodes) {
|
||||
checkEdge(from, node)
|
||||
assertions.assertContainsElements(from.followingNodes, node)
|
||||
}
|
||||
if (node.followingNodes.isEmpty() && node.previousNodes.isEmpty()) {
|
||||
throw AssertionError("Unconnected CFG node: $node")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val cfgKinds = listOf(EdgeKind.DeadForward, EdgeKind.CfgForward, EdgeKind.DeadBackward, EdgeKind.CfgBackward)
|
||||
|
||||
private fun checkEdge(from: CFGNode<*>, to: CFGNode<*>) {
|
||||
assertions.assertContainsElements(from.followingNodes, to)
|
||||
assertions.assertContainsElements(to.previousNodes, from)
|
||||
val fromKind = from.outgoingEdges.getValue(to).kind
|
||||
val toKind = to.incomingEdges.getValue(from).kind
|
||||
assertions.assertEquals(fromKind, toKind)
|
||||
if (from.isDead && to.isDead) {
|
||||
assertions.assertContainsElements(cfgKinds, toKind)
|
||||
assertions.assertFalse(node.followingNodes.isEmpty() && node.previousNodes.isEmpty()) { "Unconnected CFG node: $node" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +46,7 @@ class FirCfgConsistencyChecker(private val assertions: Assertions) : FirVisitorV
|
||||
for (node in graph.nodes) {
|
||||
for (previousNode in node.previousNodes) {
|
||||
if (previousNode.owner != graph) continue
|
||||
if (!node.incomingEdges.getValue(previousNode).kind.isBack) {
|
||||
if (!node.edgeFrom(previousNode).kind.isBack) {
|
||||
assertions.assertTrue(previousNode in visited)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user