FIR CFG: add union nodes
Quick quiz:
Q: In a CFG, what does `a -> b -> c -> d` mean?
A: `a`, then `b`, then `c`, then `d`.
Q: In a CFG, what does `a -> b -> d; a -> c -> d` mean?
A: `a`, then `b` or `c`, then `d`.
Q: So how do you encode "a, then (b, then c) or (c, then b), then d`?
A: You can't.
Problem is, you need to, because that's what `a; run2({ b }, { c }); d`
does when `run2` has a contract that it calls both its lambda arguments
in-place: `shuffle(listOf(block1, block2)).forEach { it() }` is a
perfectly valid implementation for it, as little sense as that makes.
So that's what union nodes solve. When a node implements
`UnionNodeMarker`, its inputs are interpreted as "all visited in some
order" instead of the normal "one of the inputs is visited".
Currently this is used for data flow. It *should* also be used for
control flow, but it isn't. But it should be. But that's not so easy.
BTW, `try` exit is NOT a union node; although lambdas in one branch can
be completed according to types' of lambdas in another, data does not
flow between the branches anyway (since we don't know how much of the
`try` executed before jumping into `catch`, and `catch`es are mutually
exclusive) so a `try` expression is more like `when` than a function
call with called-in-place-exactly-once arguments. The fact that
`exitTryExpression` used `processUnionOfArguments` in a weird way
should've hinted at that, but now we know for certain.
This commit is contained in:
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.fir.resolve.dfa.PersistentFlow
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.visitors.FirTransformer
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.runIf
|
||||
|
||||
@RequiresOptIn
|
||||
@@ -134,9 +133,14 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
|
||||
@CfgInternals
|
||||
fun updateDeadStatus() {
|
||||
isDead = incomingEdges.size == previousNodes.size && incomingEdges.values.all {
|
||||
it.kind == EdgeKind.DeadForward || !it.kind.usedInCfa
|
||||
}
|
||||
isDead = if (this is UnionNodeMarker)
|
||||
incomingEdges.values.any {
|
||||
it.kind == EdgeKind.DeadForward
|
||||
}
|
||||
else
|
||||
incomingEdges.size == previousNodes.size && incomingEdges.values.all {
|
||||
it.kind == EdgeKind.DeadForward || !it.kind.usedInCfa
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R
|
||||
@@ -161,6 +165,12 @@ val CFGNode<*>.lastPreviousNode: CFGNode<*> get() = previousNodes.last()
|
||||
interface EnterNodeMarker
|
||||
interface ExitNodeMarker
|
||||
|
||||
// a ---> b ---> d
|
||||
// \-> c -/
|
||||
// Normal CFG semantics: a, then either b or c, then d
|
||||
// If d is an instance of this interface: a, then *both* b and c in some unknown order, then d
|
||||
interface UnionNodeMarker
|
||||
|
||||
// ----------------------------------- EnterNode for declaration with CFG -----------------------------------
|
||||
|
||||
sealed class CFGNodeWithSubgraphs<out E : FirElement>(owner: ControlFlowGraph, level: Int, id: Int) : CFGNode<E>(owner, level, id) {
|
||||
@@ -242,12 +252,6 @@ class PostponedLambdaExitNode(owner: ControlFlowGraph, override val fir: FirAnon
|
||||
}
|
||||
}
|
||||
|
||||
class UnionFunctionCallArgumentsNode(owner: ControlFlowGraph, override val fir: FirElement, level: Int, id: Int) : CFGNode<FirElement>(owner, level, id) {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitUnionFunctionCallArgumentsNode(this, data)
|
||||
}
|
||||
}
|
||||
|
||||
class MergePostponedLambdaExitsNode(owner: ControlFlowGraph, override val fir: FirElement, level: Int, id: Int) : CFGNode<FirElement>(owner, level, id) {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitMergePostponedLambdaExitsNode(this, data)
|
||||
@@ -661,7 +665,8 @@ class ConstExpressionNode(owner: ControlFlowGraph, override val fir: FirConstExp
|
||||
|
||||
// ----------------------------------- Check not null call -----------------------------------
|
||||
|
||||
class CheckNotNullCallNode(owner: ControlFlowGraph, override val fir: FirCheckNotNullCall, level: Int, id: Int) : CFGNode<FirCheckNotNullCall>(owner, level, id) {
|
||||
class CheckNotNullCallNode(owner: ControlFlowGraph, override val fir: FirCheckNotNullCall, level: Int, id: Int)
|
||||
: CFGNode<FirCheckNotNullCall>(owner, level, id), UnionNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitCheckNotNullCallNode(this, data)
|
||||
}
|
||||
@@ -689,11 +694,8 @@ class ResolvedQualifierNode(
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionCallNode(
|
||||
owner: ControlFlowGraph,
|
||||
override val fir: FirFunctionCall,
|
||||
level: Int, id: Int
|
||||
) : CFGNode<FirFunctionCall>(owner, level, id) {
|
||||
class FunctionCallNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int, id: Int)
|
||||
: CFGNode<FirFunctionCall>(owner, level, id), UnionNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitFunctionCallNode(this, data)
|
||||
}
|
||||
@@ -715,23 +717,15 @@ class GetClassCallNode(owner: ControlFlowGraph, override val fir: FirGetClassCal
|
||||
}
|
||||
}
|
||||
|
||||
class DelegatedConstructorCallNode(
|
||||
owner: ControlFlowGraph,
|
||||
override val fir: FirDelegatedConstructorCall,
|
||||
level: Int,
|
||||
id: Int
|
||||
) : CFGNode<FirDelegatedConstructorCall>(owner, level, id) {
|
||||
class DelegatedConstructorCallNode(owner: ControlFlowGraph, override val fir: FirDelegatedConstructorCall, level: Int, id: Int)
|
||||
: CFGNode<FirDelegatedConstructorCall>(owner, level, id), UnionNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitDelegatedConstructorCallNode(this, data)
|
||||
}
|
||||
}
|
||||
|
||||
class StringConcatenationCallNode(
|
||||
owner: ControlFlowGraph,
|
||||
override val fir: FirStringConcatenationCall,
|
||||
level: Int,
|
||||
id: Int
|
||||
) : CFGNode<FirStringConcatenationCall>(owner, level, id) {
|
||||
class StringConcatenationCallNode(owner: ControlFlowGraph, override val fir: FirStringConcatenationCall, level: Int, id: Int)
|
||||
: CFGNode<FirStringConcatenationCall>(owner, level, id), UnionNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitStringConcatenationCallNode(this, data)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ fun CFGNode<*>.render(): String =
|
||||
|
||||
is AnonymousFunctionExpressionExitNode -> "Exit anonymous function expression"
|
||||
|
||||
is UnionFunctionCallArgumentsNode -> "Call arguments union"
|
||||
is MergePostponedLambdaExitsNode -> "Merge postponed lambda exits"
|
||||
|
||||
is ClassEnterNode -> "Enter class ${owner.name}"
|
||||
|
||||
+6
-8
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.fir.resolve.dfa.cfg
|
||||
abstract class ControlFlowGraphVisitor<out R, in D> {
|
||||
abstract fun visitNode(node: CFGNode<*>, data: D): R
|
||||
|
||||
abstract fun <T> visitUnionNode(node: T, data: D): R where T : CFGNode<*>, T : UnionNodeMarker
|
||||
|
||||
// ----------------------------------- Simple function -----------------------------------
|
||||
|
||||
open fun visitFunctionEnterNode(node: FunctionEnterNode, data: D): R {
|
||||
@@ -42,10 +44,6 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitUnionFunctionCallArgumentsNode(node: UnionFunctionCallArgumentsNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitMergePostponedLambdaExitsNode(node: MergePostponedLambdaExitsNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
}
|
||||
@@ -296,7 +294,7 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
|
||||
// ----------------------------------- Check not null call -----------------------------------
|
||||
|
||||
open fun visitCheckNotNullCallNode(node: CheckNotNullCallNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
return visitUnionNode(node, data)
|
||||
}
|
||||
|
||||
// ----------------------------------- Resolvable call -----------------------------------
|
||||
@@ -310,7 +308,7 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
|
||||
}
|
||||
|
||||
open fun visitFunctionCallNode(node: FunctionCallNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
return visitUnionNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitCallableReferenceNode(node: CallableReferenceNode, data: D): R {
|
||||
@@ -322,11 +320,11 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
|
||||
}
|
||||
|
||||
open fun visitDelegatedConstructorCallNode(node: DelegatedConstructorCallNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
return visitUnionNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitStringConcatenationCallNode(node: StringConcatenationCallNode, data: D): R {
|
||||
return visitNode(node, data)
|
||||
return visitUnionNode(node, data)
|
||||
}
|
||||
|
||||
open fun visitThrowExceptionNode(node: ThrowExceptionNode, data: D): R {
|
||||
|
||||
+11
-13
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.fir.resolve.dfa.cfg
|
||||
abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothing?>() {
|
||||
abstract fun visitNode(node: CFGNode<*>)
|
||||
|
||||
// ----------------------------------- Simple function -----------------------------------
|
||||
abstract fun <T> visitUnionNode(node: T) where T : CFGNode<*>, T : UnionNodeMarker
|
||||
|
||||
// ----------------------------------- Simple function -----------------------------------
|
||||
|
||||
open fun visitFunctionEnterNode(node: FunctionEnterNode) {
|
||||
visitNode(node)
|
||||
@@ -28,10 +30,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
visitNode(node)
|
||||
}
|
||||
|
||||
open fun visitUnionFunctionCallArgumentsNode(node: UnionFunctionCallArgumentsNode) {
|
||||
visitNode(node)
|
||||
}
|
||||
|
||||
open fun visitMergePostponedLambdaExitsNode(node: MergePostponedLambdaExitsNode) {
|
||||
visitNode(node)
|
||||
}
|
||||
@@ -232,7 +230,7 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
// ----------------------------------- Check not null call -----------------------------------
|
||||
|
||||
open fun visitCheckNotNullCallNode(node: CheckNotNullCallNode) {
|
||||
visitNode(node)
|
||||
visitUnionNode(node)
|
||||
}
|
||||
|
||||
// ----------------------------------- Resolvable call -----------------------------------
|
||||
@@ -246,15 +244,15 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
}
|
||||
|
||||
open fun visitFunctionCallNode(node: FunctionCallNode) {
|
||||
visitNode(node)
|
||||
visitUnionNode(node)
|
||||
}
|
||||
|
||||
open fun visitDelegatedConstructorCallNode(node: DelegatedConstructorCallNode) {
|
||||
visitNode(node)
|
||||
visitUnionNode(node)
|
||||
}
|
||||
|
||||
open fun visitStringConcatenationCallNode(node: StringConcatenationCallNode) {
|
||||
visitNode(node)
|
||||
visitUnionNode(node)
|
||||
}
|
||||
|
||||
open fun visitThrowExceptionNode(node: ThrowExceptionNode) {
|
||||
@@ -297,6 +295,10 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
visitNode(node)
|
||||
}
|
||||
|
||||
final override fun <T> visitUnionNode(node: T, data: Nothing?) where T : CFGNode<*>, T : UnionNodeMarker {
|
||||
visitUnionNode(node)
|
||||
}
|
||||
|
||||
// ----------------------------------- Simple function -----------------------------------
|
||||
|
||||
final override fun visitFunctionEnterNode(node: FunctionEnterNode, data: Nothing?) {
|
||||
@@ -317,10 +319,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
|
||||
visitPostponedLambdaExitNode(node)
|
||||
}
|
||||
|
||||
final override fun visitUnionFunctionCallArgumentsNode(node: UnionFunctionCallArgumentsNode, data: Nothing?) {
|
||||
visitUnionFunctionCallArgumentsNode(node)
|
||||
}
|
||||
|
||||
final override fun visitMergePostponedLambdaExitsNode(node: MergePostponedLambdaExitsNode, data: Nothing?) {
|
||||
visitMergePostponedLambdaExitsNode(node)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user