FIR DFA: fork flow everywhere

In theory, forking persistent flows should be cheap because of object
reuse, so the proposal here is to start from scratch and prove
redundancy of forks on a case-by-case basis. Something something better
safe than sorry.

^KT-28333 tag fixed-in-k2
^KT-28489 tag fixed-in-k2
This commit is contained in:
pyos
2022-11-08 21:33:40 +01:00
committed by teamcity
parent 757921e63e
commit edaca59d83
17 changed files with 554 additions and 683 deletions
@@ -30,17 +30,14 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
transform: (Implication) -> Implication? = { it },
)
abstract fun approveStatementsInsideFlow(
flow: FLOW,
approvedStatement: OperationStatement,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean,
): FLOW
abstract fun commitOperationStatement(flow: FLOW, statement: OperationStatement, shouldRemoveSynthetics: Boolean)
abstract fun addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariable)
abstract fun recordNewAssignment(flow: FLOW, variable: RealVariable, index: Int)
abstract fun copyAllInformation(from: FLOW, to: FLOW)
protected abstract fun getImplicationsWithVariable(flow: FLOW, variable: DataFlowVariable): Collection<Implication>
protected abstract fun ConeKotlinType.isAcceptableForSmartcast(): Boolean
@@ -26,7 +26,6 @@ class PersistentFlow : Flow {
var approvedTypeStatements: PersistentApprovedTypeStatements
var logicStatements: PersistentImplications
val level: Int
var approvedTypeStatementsDiff: PersistentApprovedTypeStatements = persistentHashMapOf()
/*
* val x = a
@@ -75,12 +74,18 @@ class PersistentFlow : Flow {
abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSystem<PersistentFlow>(context) {
abstract val variableStorage: VariableStorageImpl
override fun createEmptyFlow(): PersistentFlow {
return PersistentFlow()
}
override fun createEmptyFlow(): PersistentFlow =
PersistentFlow()
override fun forkFlow(flow: PersistentFlow): PersistentFlow {
return PersistentFlow(flow)
override fun forkFlow(flow: PersistentFlow): PersistentFlow =
PersistentFlow(flow)
override fun copyAllInformation(from: PersistentFlow, to: PersistentFlow) {
to.approvedTypeStatements = from.approvedTypeStatements
to.logicStatements = from.logicStatements
to.directAliasMap = from.directAliasMap
to.backwardsAliasMap = from.backwardsAliasMap
to.assignmentIndex = from.assignmentIndex
}
override fun joinFlow(flows: Collection<PersistentFlow>): PersistentFlow =
@@ -92,10 +97,10 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
private fun foldFlow(flows: Collection<PersistentFlow>, allExecute: Boolean): PersistentFlow {
when (flows.size) {
0 -> return createEmptyFlow()
1 -> return flows.first()
1 -> return forkFlow(flows.first())
}
val commonFlow = flows.reduce(::lowestCommonFlow)
val commonFlow = forkFlow(flows.reduce(::lowestCommonFlow))
// Aliases that have occurred before branching should already be in `commonFlow`,
// but it might be useful to also add aliases that happen in all branches, e.g.
// the aliasing of `y` to `a.x` after `if (p) { y = a.x } else { y = a.x }`.
@@ -137,9 +142,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
commonFlow.approvedTypeStatements += statements
if (commonFlow.previousFlow != null) {
commonFlow.approvedTypeStatementsDiff += statements
}
for ((alias, underlyingVariable) in commonAliases) {
addLocalVariableAlias(commonFlow, alias, underlyingVariable)
@@ -168,7 +170,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
assert(variable !in backwardsAliasMap)
assert(variable !in logicStatements)
assert(variable !in approvedTypeStatements)
assert(variable !in approvedTypeStatementsDiff)
}
// `variable.dependentVariables` is not separated by flow, so it may be non-empty if aliasing of this variable
// was broken in another flow. However, in *this* flow dependent variables should have no information attached to them.
@@ -194,7 +195,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
logicStatements = logicStatements.replaceVariable(variable, replacementOrNext)
approvedTypeStatements = approvedTypeStatements.replaceVariable(variable, replacementOrNext)
approvedTypeStatementsDiff = approvedTypeStatementsDiff.replaceVariable(variable, replacementOrNext)
if (aliases != null) {
backwardsAliasMap -= variable
if (replacementOrNext != null) {
@@ -214,24 +214,23 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
override fun addTypeStatement(flow: PersistentFlow, statement: TypeStatement) {
if (statement.isEmpty) return
with(flow) {
approvedTypeStatements = approvedTypeStatements.addTypeStatement(statement)
if (previousFlow != null) {
approvedTypeStatementsDiff = approvedTypeStatementsDiff.addTypeStatement(statement)
}
if (statement.variable.isThisReference) {
processUpdatedReceiverVariable(flow, statement.variable)
}
if (statement.exactType.isEmpty()) return
val variable = statement.variable
val oldExactType = flow.approvedTypeStatements[variable]?.exactType
val newExactType = oldExactType?.addAll(statement.exactType) ?: statement.exactType.toPersistentSet()
if (newExactType === oldExactType) return
flow.approvedTypeStatements = flow.approvedTypeStatements.put(variable, PersistentTypeStatement(variable, newExactType))
if (variable.isThisReference) {
processUpdatedReceiverVariable(flow, variable)
}
}
private fun PersistentApprovedTypeStatements.addTypeStatement(info: TypeStatement): PersistentApprovedTypeStatements =
put(info.variable, { info.toPersistent() }, { and(listOf(it, info)).toPersistent() })
override fun addImplication(flow: PersistentFlow, implication: Implication) {
if ((implication.effect as? TypeStatement)?.isEmpty == true) return
if (implication.condition == implication.effect) return
val effect = implication.effect
if (effect == implication.condition) return
if (effect is TypeStatement && (effect.isEmpty ||
flow.approvedTypeStatements[effect.variable]?.exactType?.containsAll(effect.exactType) == true)
) return
val variable = implication.condition.variable
flow.logicStatements = flow.logicStatements.put(variable, { persistentListOf(implication) }, { it + implication })
}
@@ -257,35 +256,10 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
}
override fun approveStatementsInsideFlow(
flow: PersistentFlow,
approvedStatement: OperationStatement,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean
): PersistentFlow {
val approvedFacts = approveOperationStatementsInternal(
flow,
approvedStatement,
initialStatements = null,
shouldRemoveSynthetics
)
val resultFlow = if (shouldForkFlow) forkFlow(flow) else flow
if (approvedFacts.isEmpty) return resultFlow
val updatedReceivers = mutableSetOf<RealVariable>()
approvedFacts.asMap().forEach { (variable, infos) ->
if (variable.isThisReference) {
updatedReceivers += variable
}
addTypeStatement(resultFlow, and(infos))
override fun commitOperationStatement(flow: PersistentFlow, statement: OperationStatement, shouldRemoveSynthetics: Boolean) {
approveOperationStatementsInternal(flow, statement, initialStatements = null, shouldRemoveSynthetics).values.forEach {
addTypeStatement(flow, it)
}
updatedReceivers.forEach {
processUpdatedReceiverVariable(resultFlow, it)
}
return resultFlow
}
private fun approveOperationStatementsInternal(
@@ -293,55 +267,40 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
approvedStatement: OperationStatement,
initialStatements: Collection<Implication>?,
shouldRemoveSynthetics: Boolean
): ArrayListMultimap<RealVariable, TypeStatement> {
val approvedFacts: ArrayListMultimap<RealVariable, TypeStatement> = ArrayListMultimap.create()
val approvedStatements = LinkedList<OperationStatement>().apply { this += approvedStatement }
approveOperationStatementsInternal(flow, approvedStatements, initialStatements, shouldRemoveSynthetics, approvedFacts)
return approvedFacts
}
private fun approveOperationStatementsInternal(
flow: PersistentFlow,
approvedStatements: LinkedList<OperationStatement>,
initialStatements: Collection<Implication>?,
shouldRemoveSynthetics: Boolean,
approvedTypeStatements: ArrayListMultimap<RealVariable, TypeStatement>
) {
if (approvedStatements.isEmpty()) return
val approvedOperationStatements = mutableSetOf<OperationStatement>()
): TypeStatements {
val approvedTypeStatements: ArrayListMultimap<RealVariable, TypeStatement> = ArrayListMultimap.create()
val queue = LinkedList<OperationStatement>().apply { this += approvedStatement }
val approved = mutableSetOf<OperationStatement>()
var firstIteration = true
while (approvedStatements.isNotEmpty()) {
@Suppress("NAME_SHADOWING")
val approvedStatement: OperationStatement = approvedStatements.removeFirst()
while (queue.isNotEmpty()) {
val next: OperationStatement = queue.removeFirst()
// Defense from cycles in facts
if (!approvedOperationStatements.add(approvedStatement)) {
continue
}
if (!approved.add(next)) continue
val variable = next.variable
val statements = initialStatements?.takeIf { firstIteration }
?: flow.logicStatements[approvedStatement.variable]?.takeIf { it.isNotEmpty() }
?: flow.logicStatements[variable]?.takeIf { it.isNotEmpty() }
?: continue
if (shouldRemoveSynthetics && approvedStatement.variable.isSynthetic()) {
flow.logicStatements -= approvedStatement.variable
if (shouldRemoveSynthetics && variable.isSynthetic()) {
flow.logicStatements -= variable
}
for (statement in statements) {
if (statement.condition == approvedStatement) {
if (statement.condition == next) {
when (val effect = statement.effect) {
is OperationStatement -> approvedStatements += effect
is OperationStatement -> queue += effect
is TypeStatement -> approvedTypeStatements.put(effect.variable, effect)
}
}
}
firstIteration = false
}
return approvedTypeStatements.asMap().mapValues { and(it.value) }
}
override fun approveOperationStatement(
flow: PersistentFlow,
approvedStatement: OperationStatement,
statementsForVariable: Collection<Implication>?
): TypeStatements =
approveOperationStatementsInternal(flow, approvedStatement, statementsForVariable, shouldRemoveSynthetics = false)
.asMap().mapValues { and(it.value) }
): TypeStatements = approveOperationStatementsInternal(flow, approvedStatement, statementsForVariable, shouldRemoveSynthetics = false)
override fun collectInfoForBooleanOperator(
leftFlow: PersistentFlow,
@@ -352,7 +311,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
return InfoForBooleanOperator(
leftFlow.logicStatements[leftVariable] ?: emptyList(),
rightFlow.logicStatements[rightVariable] ?: emptyList(),
rightFlow.approvedTypeStatementsDiff
rightFlow.approvedTypeStatements
)
}
@@ -780,8 +780,7 @@ class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallEx
}
}
class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
val lastPreviousNode: CFGNode<*> get() = previousNodes.last()
val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1)
val lastNodeInNotNullCase: CFGNode<*>? get() = previousNodes.getOrNull(1)
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitExitSafeCallNode(this, data)