FIR DFA: try to group mutations to persistent data structures

Thinking of monomorphizing `Flow` (= `PersistentFlow`) and instead
adding a completely separate `MutableFlow` version; then `joinFlow`
would produce a mutable one, data flow analysis would add some
statements, convert to a persistent `Flow` and proceed to the next
node.
This commit is contained in:
pyos
2022-11-12 22:04:01 +01:00
committed by teamcity
parent ea1caf0955
commit ae1048f8a9
3 changed files with 94 additions and 67 deletions
@@ -919,7 +919,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
flow, conditionalEffect.condition, argumentVariables, substitutor, removeApprovedOrImpossible = operation == null
) ?: continue // TODO: do what if the result is known to be false?
if (operation == null) {
statements.values.forEach { flow.addTypeStatement(it) }
flow.addAllStatements(statements)
} else {
val functionCallVariable = variableStorage.getOrCreate(flow, qualifiedAccess)
flow.addAllConditionally(OperationStatement(functionCallVariable, operation), statements)
@@ -1270,16 +1270,23 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
}
private fun FLOW.addAllConditionally(condition: OperationStatement, statements: TypeStatements) {
statements.values.forEach { addImplication(condition implies it) }
}
private fun FLOW.commitOperationStatement(statement: OperationStatement) {
logicSystem.approveOperationStatement(this, statement, removeApprovedOrImpossible = true).values.forEach {
addTypeStatement(it)
private fun FLOW.addAllStatements(statements: TypeStatements) {
val newStatements = logicSystem.addTypeStatements(this, statements)
if (this === currentReceiverState) {
for (newStatement in newStatements) {
if (newStatement.variable.isThisReference) {
receiverUpdated(newStatement.variable.identifier.symbol, newStatement)
}
}
}
}
private fun FLOW.addAllConditionally(condition: OperationStatement, statements: TypeStatements) =
statements.values.forEach { addImplication(condition implies it) }
private fun FLOW.commitOperationStatement(statement: OperationStatement) =
addAllStatements(logicSystem.approveOperationStatement(this, statement, removeApprovedOrImpossible = true))
private val CFGNode<*>.previousFlow: FLOW
get() = firstPreviousNode.flow
}
@@ -17,6 +17,7 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
// -------------------------------- Flow mutators --------------------------------
// Returns all known information about the variable, or null if unchanged by this statement:
abstract fun addTypeStatement(flow: FLOW, statement: TypeStatement): TypeStatement?
abstract fun addTypeStatements(flow: FLOW, statements: TypeStatements): List<TypeStatement>
abstract fun addImplication(flow: FLOW, implication: Implication)
abstract fun addLocalVariableAlias(flow: FLOW, alias: RealVariable, underlyingVariable: RealVariable)
abstract fun recordNewAssignment(flow: FLOW, variable: RealVariable, index: Int)
@@ -96,33 +96,8 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
1 -> return forkFlow(flows.first())
}
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 }`.
val commonAliases = computeCommonAliases(flows)
// Computing the statements for these aliases is redundant as the result is equal
// to the statements for whatever they are aliasing.
val variables = flows.flatMapTo(mutableSetOf()) { it.approvedTypeStatements.keys + it.directAliasMap.keys } - commonAliases.keys
val statements = variables.mapNotNull computeStatement@{ variable ->
val statement = if (allExecute) {
// All input flows execute in some order. If none of the flows reassign, i.e. the only key
// in `byAssignment` is `commonFlow`'s assignment index, then all statements are true.
// Otherwise, one of the assignments executed last, but we don't know which, and the flows
// that don't reassign should be ignored because they may have executed before or after that.
val byAssignment = flows.groupByTo(mutableMapOf()) { it.assignmentIndex[variable] ?: -1 }
if (byAssignment.size > 1) {
byAssignment.remove(commonFlow.assignmentIndex[variable] ?: -1)
}
or(byAssignment.values.map { flowSubset ->
and(flowSubset.mapNotNull { it.getTypeStatement(variable) }) ?: return@computeStatement null
})
} else {
// One input flow executes - one set of statements is true, others might be false.
or(flows.map { it.getTypeStatement(variable) ?: return@computeStatement null })
}
if (statement?.isEmpty == false) variable to statement.toPersistent() else null
}
val commonFlow = flows.reduce(::lowestCommonFlow)
val result = forkFlow(commonFlow)
// If a variable was reassigned in one branch, it was reassigned at the join point.
val reassignedVariables = mutableMapOf<RealVariable, Int>()
@@ -136,22 +111,54 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
}
}
for ((variable, index) in reassignedVariables) {
recordNewAssignment(commonFlow, variable, index)
recordNewAssignment(result, variable, index)
}
commonFlow.approvedTypeStatements += statements
for ((alias, underlyingVariable) in commonAliases) {
addLocalVariableAlias(commonFlow, alias, underlyingVariable)
// TODO: if `allExecute`, then all aliases from all flows are valid so long as other flows don't have a contradicting one
for ((from, to) in flows.first().directAliasMap) {
// If `from -> to` is still in `result` (was not removed by the above code), then it is also in all `flows`,
// as the only way to break aliasing is by reassignment.
if (result.directAliasMap[from] != to && flows.all { it.directAliasMap[from] == to }) {
// if (p) { y = x } else { y = x } <-- after `if`, `y -> x` is in all `flows`, but not in `result`
// (which was forked from the flow before the `if`)
addLocalVariableAlias(result, from, to)
}
}
return commonFlow
val approvedTypeStatements = result.approvedTypeStatements.builder()
val allVariablesWithStatements = flows.flatMapTo(mutableSetOf()) { it.approvedTypeStatements.keys + it.directAliasMap.keys }
allVariablesWithStatements.forEach computeStatement@{ variable ->
val statement = if (variable in result.directAliasMap) {
return@computeStatement // statements about alias == statements about aliased variable
} else if (!allExecute) {
// if (condition) { /* x: S1 */ } else { /* x: S2 */ } // -> x: S1 | S2
or(flows.mapTo(mutableSetOf()) { it.getTypeStatement(variable) ?: return@computeStatement })
} else if (variable !in reassignedVariables) {
// callAllInSomeOrder({ /* x: S1 */ }, { /* x: S2 */ }) // -> x: S1 & S2
and(flows.mapNotNullTo(mutableSetOf()) { it.getTypeStatement(variable) })
} else {
// callAllInSomeOrder({ x = ...; /* x: S1 */ }, { x = ...; /* x: S2 */ }, { /* x: S3 */ }) // -> x: S1 | S2
val byAssignment =
flows.groupByTo(mutableMapOf(), { it.assignmentIndex[variable] ?: -1 }, { it.getTypeStatement(variable) })
// This throws out S3 from the above example, as the flow that makes that statement is unordered w.r.t. the other two:
byAssignment.remove(commonFlow.assignmentIndex[variable] ?: -1)
// In the above example each list in `byAssignment.values` only has one entry, but in general we can have something like
// A -> B (assigns) -> C (does not assign)
// \ \--> D (does not assign)
// \-> E (assigns)
// in which case for {C, D, E} the result is (C && D) || E. Not sure that kind of control flow can exist
// without intermediate nodes being created for (C && D) though.
or(byAssignment.values.mapTo(mutableSetOf()) { and(it.filterNotNull()) ?: return@computeStatement })
}
if (statement?.isNotEmpty == true) {
approvedTypeStatements[variable] = statement.toPersistent()
}
}
result.approvedTypeStatements = approvedTypeStatements.build()
// TODO: compute common implications?
return result
}
private fun computeCommonAliases(flows: Collection<PersistentFlow>): Map<RealVariable, RealVariable> =
flows.first().directAliasMap.filterTo(mutableMapOf()) { (variable, alias) ->
flows.all { it.directAliasMap[variable] == alias }
}
override fun addLocalVariableAlias(flow: PersistentFlow, alias: RealVariable, underlyingVariable: RealVariable) {
flow.addAliases(persistentSetOf(alias), flow.unwrapVariable(underlyingVariable))
}
@@ -206,20 +213,32 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
private fun PersistentFlow.addAliases(aliases: PersistentSet<RealVariable>, target: RealVariable) {
val withoutSelf = aliases - target
if (withoutSelf.isNotEmpty()) {
directAliasMap += withoutSelf.map { it to target }
directAliasMap = withoutSelf.associateWithTo(directAliasMap.builder()) { target }.build()
backwardsAliasMap = backwardsAliasMap.put(target, { withoutSelf }, { it + withoutSelf })
}
}
override fun addTypeStatement(flow: PersistentFlow, statement: TypeStatement): TypeStatement? {
private fun PersistentFlow.completeStatement(statement: TypeStatement): PersistentTypeStatement? {
if (statement.exactType.isEmpty()) return null
val variable = statement.variable
val oldExactType = flow.approvedTypeStatements[variable]?.exactType
val oldExactType = approvedTypeStatements[variable]?.exactType
val newExactType = oldExactType?.addAll(statement.exactType) ?: statement.exactType.toPersistentSet()
if (newExactType === oldExactType) return null
val newStatement = PersistentTypeStatement(variable, newExactType)
flow.approvedTypeStatements = flow.approvedTypeStatements.put(variable, newStatement)
return newStatement
return PersistentTypeStatement(variable, newExactType)
}
override fun addTypeStatement(flow: PersistentFlow, statement: TypeStatement): TypeStatement? =
flow.completeStatement(statement)?.also {
flow.approvedTypeStatements = flow.approvedTypeStatements.put(it.variable, it)
}
override fun addTypeStatements(flow: PersistentFlow, statements: TypeStatements): List<TypeStatement> {
val builder = flow.approvedTypeStatements.builder()
val result = statements.values.mapNotNull { statement ->
flow.completeStatement(statement)?.also { builder[it.variable] = it }
}
flow.approvedTypeStatements = builder.build()
return result
}
override fun addImplication(flow: PersistentFlow, implication: Implication) {
@@ -239,17 +258,17 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
shouldRemoveOriginalStatements: Boolean,
transform: (Implication) -> Implication?
) {
with(flow) {
val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return
val newStatements = statements.mapNotNull {
val newStatement = OperationStatement(newVariable, it.condition.operation) implies it.effect
transform(newStatement)
}.toPersistentList()
if (shouldRemoveOriginalStatements) {
logicStatements -= originalVariable
}
logicStatements = logicStatements.put(newVariable, logicStatements[newVariable]?.let { it + newStatements } ?: newStatements)
val statements = flow.logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return
val existing = flow.logicStatements[newVariable] ?: persistentListOf()
val result = statements.mapNotNullTo(existing.builder()) {
// TODO: rethink this API - technically it permits constructing invalid flows
// (transform can replace the variable in the condition with anything)
transform(OperationStatement(newVariable, it.condition.operation) implies it.effect)
}.build()
if (shouldRemoveOriginalStatements) {
flow.logicStatements -= originalVariable
}
flow.logicStatements = flow.logicStatements.put(newVariable, result)
}
private val nullableNothingType = context.session.builtinTypes.nullableNothingType.type
@@ -263,6 +282,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
val result = mutableMapOf<RealVariable, MutableTypeStatement>()
val queue = LinkedList<OperationStatement>().apply { this += approvedStatement }
val approved = mutableSetOf<OperationStatement>()
val logicStatements = flow.logicStatements.builder()
while (queue.isNotEmpty()) {
val next = queue.removeFirst()
// Defense from cycles in facts
@@ -275,7 +295,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
result.getOrPut(variable) { MutableTypeStatement(variable) }.exactType.add(impliedType)
}
val statements = flow.logicStatements[variable] ?: continue
val statements = logicStatements[variable] ?: continue
val stillUnknown = statements.removeAll {
val knownValue = it.condition.operation.valueIfKnown(operation)
if (knownValue == true) {
@@ -288,11 +308,12 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
removeApprovedOrImpossible && knownValue != null
}
if (stillUnknown.isEmpty()) {
flow.logicStatements -= variable
logicStatements.remove(variable)
} else if (stillUnknown != statements) {
flow.logicStatements = flow.logicStatements.put(variable, stillUnknown)
logicStatements[variable] = stillUnknown
}
}
flow.logicStatements = logicStatements.build()
return result
}
@@ -300,8 +321,6 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
removeAllAboutVariable(flow, variable)
flow.assignmentIndex = flow.assignmentIndex.put(variable, index)
}
// --------------------------------------------------------------------\
}
private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): PersistentFlow {