From cc973f8c3f21f8ea6c04718b87512844c749ad70 Mon Sep 17 00:00:00 2001 From: pyos Date: Wed, 30 Nov 2022 16:16:08 +0100 Subject: [PATCH] FIR DFA: merge LogicSystem and PersistentLogicSystem --- .../analysis/cfa/FirReturnsImpliesAnalyzer.kt | 5 +- .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 4 +- .../kotlin/fir/resolve/dfa/LogicSystem.kt | 325 ++++++++++++++++- .../fir/resolve/dfa/PersistentLogicSystem.kt | 327 ------------------ 4 files changed, 311 insertions(+), 350 deletions(-) delete mode 100644 compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt index 6310c3a6f04..3ca661f8414 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirReturnsImpliesAnalyzer.kt @@ -44,12 +44,9 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() { if (effects.isNullOrEmpty()) return - val logicSystem = object : PersistentLogicSystem(context.session.typeContext) { + val logicSystem = object : LogicSystem(context.session.typeContext) { override val variableStorage: VariableStorageImpl get() = throw IllegalStateException("shouldn't be called") - - override fun ConeKotlinType.isAcceptableForSmartcast(): Boolean = - !isNullableNothing } effects.forEach { effect -> diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index 73da6eab2f6..fb29fce9e56 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -83,8 +83,8 @@ abstract class FirDataFlowAnalyzer( receiverStack.replaceReceiverType(index, info.smartCastedType(typeContext, originalType)) } - override val logicSystem: PersistentLogicSystem = - object : PersistentLogicSystem(components.session.typeContext) { + override val logicSystem: LogicSystem = + object : LogicSystem(components.session.typeContext) { override val variableStorage: VariableStorageImpl get() = dataFlowAnalyzerContext.variableStorage diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt index 787adc90f8a..3e731410781 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/LogicSystem.kt @@ -5,37 +5,271 @@ package org.jetbrains.kotlin.fir.resolve.dfa +import kotlinx.collections.immutable.* import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.types.AbstractTypeChecker +import java.util.* +import kotlin.math.max -abstract class LogicSystem(protected val context: ConeInferenceContext) { - abstract fun joinFlow(flows: Collection, union: Boolean): MutableFlow +abstract class LogicSystem(private val context: ConeInferenceContext) { + private val nullableNothingType = context.session.builtinTypes.nullableNothingType.type + private val anyType = context.session.builtinTypes.anyType.type - // Returns all known information about the variable, or null if unchanged by this statement: - abstract fun addTypeStatement(flow: MutableFlow, statement: TypeStatement): TypeStatement? - abstract fun addImplication(flow: MutableFlow, implication: Implication) - abstract fun addLocalVariableAlias(flow: MutableFlow, alias: RealVariable, underlyingVariable: RealVariable) - abstract fun recordNewAssignment(flow: MutableFlow, variable: RealVariable, index: Int) - abstract fun isSameValueIn(a: PersistentFlow, b: MutableFlow, variable: RealVariable): Boolean - abstract fun isSameValueIn(a: PersistentFlow, b: PersistentFlow, variable: RealVariable): Boolean + abstract val variableStorage: VariableStorageImpl + + protected open fun ConeKotlinType.isAcceptableForSmartcast(): Boolean = + !isNullableNothing + + fun joinFlow(flows: Collection, union: Boolean): MutableFlow { + when (flows.size) { + 0 -> return MutableFlow() + 1 -> return flows.first().fork() + } + // If you're debugging this assertion error, most likely cause is that a node is not + // marked as dead when all its input edges are dead. In that case it will have an empty flow, + // and joining that with a non-empty flow from another branch will fail. + val commonFlow = flows.reduce { a, b -> a.lowestCommonAncestor(b) ?: error("no common ancestor in $a, $b") } + val result = commonFlow.fork() + result.mergeAssignments(flows) + if (union) { + result.copyNonConflictingAliases(flows, commonFlow) + } else { + result.copyCommonAliases(flows) + } + result.copyStatements(flows, commonFlow, union) + // TODO: compute common implications? + return result + } + + fun addLocalVariableAlias(flow: MutableFlow, alias: RealVariable, underlyingVariable: RealVariable) { + if (underlyingVariable == alias) return // x = x + flow.directAliasMap[alias] = underlyingVariable + flow.backwardsAliasMap[underlyingVariable] = flow.backwardsAliasMap[underlyingVariable]?.add(alias) ?: persistentSetOf(alias) + } + + fun addTypeStatement(flow: MutableFlow, statement: TypeStatement): TypeStatement? { + if (statement.exactType.isEmpty()) return null + val variable = statement.variable + val oldExactType = flow.approvedTypeStatements[variable]?.exactType + val newExactType = oldExactType?.addAll(statement.exactType) ?: statement.exactType.toPersistentSet() + if (newExactType === oldExactType) return null + return PersistentTypeStatement(variable, newExactType).also { flow.approvedTypeStatements[variable] = it } + } fun addTypeStatements(flow: MutableFlow, statements: TypeStatements): List = statements.values.mapNotNull { addTypeStatement(flow, it) } - abstract fun translateVariableFromConditionInStatements( + fun addImplication(flow: MutableFlow, implication: Implication) { + 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[variable] = flow.logicStatements[variable]?.add(implication) ?: persistentListOf(implication) + } + + fun translateVariableFromConditionInStatements( flow: MutableFlow, originalVariable: DataFlowVariable, newVariable: DataFlowVariable, - transform: (Implication) -> Implication? = { it }, - ) + transform: (Implication) -> Implication? = { it } + ) { + val statements = if (originalVariable.isSynthetic()) + flow.logicStatements.remove(originalVariable) + else + flow.logicStatements[originalVariable] + if (statements.isNullOrEmpty()) return + val existing = flow.logicStatements[newVariable] ?: persistentListOf() + flow.logicStatements[newVariable] = 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() + } - abstract fun approveOperationStatement(flow: PersistentFlow, statement: OperationStatement): TypeStatements - abstract fun approveOperationStatement( + fun approveOperationStatement(flow: PersistentFlow, statement: OperationStatement): TypeStatements = + approveOperationStatement(flow.logicStatements, statement, removeApprovedOrImpossible = false) + + fun approveOperationStatement( flow: MutableFlow, statement: OperationStatement, removeApprovedOrImpossible: Boolean, - ): TypeStatements + ): TypeStatements = approveOperationStatement(flow.logicStatements, statement, removeApprovedOrImpossible) - protected abstract fun ConeKotlinType.isAcceptableForSmartcast(): Boolean + fun recordNewAssignment(flow: MutableFlow, variable: RealVariable, index: Int) { + flow.replaceVariable(variable, null) + flow.assignmentIndex[variable] = index + } + + fun isSameValueIn(a: PersistentFlow, b: MutableFlow, variable: RealVariable): Boolean = + a.assignmentIndex[variable] == b.assignmentIndex[variable] + + fun isSameValueIn(a: PersistentFlow, b: PersistentFlow, variable: RealVariable): Boolean = + a.assignmentIndex[variable] == b.assignmentIndex[variable] + + private fun MutableFlow.mergeAssignments(flows: Collection) { + // If a variable was reassigned in one branch, it was reassigned at the join point. + val reassignedVariables = mutableMapOf() + for (flow in flows) { + for ((variable, index) in flow.assignmentIndex) { + if (assignmentIndex[variable] != index) { + // Ideally we should generate an entirely new index here, but it doesn't really + // matter; the important part is that it's different from `commonFlow.previousFlow`. + reassignedVariables[variable] = max(index, reassignedVariables[variable] ?: 0) + } + } + } + for ((variable, index) in reassignedVariables) { + recordNewAssignment(this, variable, index) + } + } + + private fun MutableFlow.copyCommonAliases(flows: Collection) { + for ((from, to) in flows.first().directAliasMap) { + // If `from -> to` is still in `this` (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 (directAliasMap[from] != to && flows.all { it.unwrapVariable(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(this, from, to) + } + } + } + + private fun MutableFlow.copyNonConflictingAliases(flows: Collection, commonFlow: PersistentFlow) { + val candidates = mutableMapOf() + for (flow in flows) { + for ((from, to) in flow.directAliasMap) { + candidates[from] = when { + // f({ a = b }, { notReassigning() }) -> a = b + commonFlow.assignmentIndex[from] == flow.assignmentIndex[from] -> continue + // f({ a = b }, { a = c }) -> a = b or c; can't express that, so just don't + from in candidates && candidates[from] != to -> null + // f({ a = b }, { a = b }) -> a = b + else -> to + } + } + } + for ((from, to) in candidates) { + addLocalVariableAlias(this, from, to ?: continue) + } + } + + private fun MutableFlow.copyStatements(flows: Collection, commonFlow: PersistentFlow, union: Boolean) { + flows.flatMapTo(mutableSetOf()) { it.knownVariables }.forEach computeStatement@{ variable -> + val statement = if (variable in directAliasMap) { + return@computeStatement // statements about alias == statements about aliased variable + } else if (!union) { + // if (condition) { /* x: S1 */ } else { /* x: S2 */ } // -> x: S1 | S2 + or(flows.mapTo(mutableSetOf()) { it.getTypeStatement(variable) ?: return@computeStatement }) + } else if (assignmentIndex[variable] == commonFlow.assignmentIndex[variable]) { + // 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() + } + } + } + + private fun MutableFlow.replaceVariable(variable: RealVariable, replacement: RealVariable?) { + val original = directAliasMap.remove(variable) + if (original != null) { + // All statements should've been made about whatever variable this is an alias to. There is nothing to replace. + if (AbstractTypeChecker.RUN_SLOW_ASSERTIONS) { + assert(variable !in backwardsAliasMap) + assert(variable !in logicStatements) + assert(variable !in approvedTypeStatements) + } + // `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. + val siblings = backwardsAliasMap.getValue(original) + if (siblings.size > 1) { + backwardsAliasMap[original] = siblings.remove(variable) + } else { + backwardsAliasMap.remove(original) + } + if (replacement != null) { + addLocalVariableAlias(this, replacement, original) + } + } else { + val aliases = backwardsAliasMap.remove(variable) + // If asked to remove the variable but there are aliases, replace with a new representative for the alias group instead. + val replacementOrNext = replacement ?: aliases?.first() + for (dependent in variable.dependentVariables) { + replaceVariable(dependent, replacementOrNext?.let { + variableStorage.copyRealVariableWithRemapping(dependent, variable, it) + }) + } + logicStatements.replaceVariable(variable, replacementOrNext) + approvedTypeStatements.replaceVariable(variable, replacementOrNext) + if (aliases != null && replacementOrNext != null) { + directAliasMap -= replacementOrNext + val withoutSelf = aliases - replacementOrNext + if (withoutSelf.isNotEmpty()) { + withoutSelf.associateWithTo(directAliasMap) { replacementOrNext } + backwardsAliasMap[replacementOrNext] = backwardsAliasMap[replacementOrNext]?.addAll(withoutSelf) ?: withoutSelf + } + } + } + } + + private fun approveOperationStatement( + logicStatements: Map>, + approvedStatement: OperationStatement, + removeApprovedOrImpossible: Boolean + ): TypeStatements { + val result = mutableMapOf() + val queue = LinkedList().apply { this += approvedStatement } + val approved = mutableSetOf() + while (queue.isNotEmpty()) { + val next = queue.removeFirst() + // Defense from cycles in facts + if (!removeApprovedOrImpossible && !approved.add(next)) continue + + val operation = next.operation + val variable = next.variable + if (variable.isReal()) { + val impliedType = if (operation == Operation.EqNull) nullableNothingType else anyType + result.getOrPut(variable) { MutableTypeStatement(variable) }.exactType.add(impliedType) + } + + val statements = logicStatements[variable] ?: continue + val stillUnknown = statements.removeAll { + val knownValue = it.condition.operation.valueIfKnown(operation) + if (knownValue == true) { + when (val effect = it.effect) { + is OperationStatement -> queue += effect + is TypeStatement -> + result.getOrPut(effect.variable) { MutableTypeStatement(effect.variable) } += effect + } + } + removeApprovedOrImpossible && knownValue != null + } + if (stillUnknown != statements && logicStatements is MutableMap) { + if (stillUnknown.isEmpty()) { + logicStatements.remove(variable) + } else { + logicStatements[variable] = stillUnknown + } + } + } + return result + } // ------------------------------- Public TypeStatement util functions ------------------------------- @@ -82,7 +316,7 @@ abstract class LogicSystem(protected val context: ConeInferenceContext) { } } - protected operator fun MutableTypeStatement.plusAssign(other: TypeStatement) { + private operator fun MutableTypeStatement.plusAssign(other: TypeStatement) { exactType += other.exactType } @@ -92,3 +326,60 @@ abstract class LogicSystem(protected val context: ConeInferenceContext) { fun or(statements: Collection): TypeStatement? = statements.singleOrNew { unifyTypes(statements.map { it.exactType })?.let { mutableSetOf(it) } ?: mutableSetOf() } } + +private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) { + is PersistentTypeStatement -> this + else -> PersistentTypeStatement(variable, exactType.toPersistentSet()) +} + +@JvmName("replaceVariableInStatements") +private fun MutableMap.replaceVariable(from: RealVariable, to: RealVariable?) { + val existing = remove(from) ?: return + if (to != null) { + put(to, existing.copy(variable = to)) + } +} + +@JvmName("replaceVariableInImplications") +private fun MutableMap>.replaceVariable(from: RealVariable, to: RealVariable?) { + val existing = remove(from) + val toReplace = entries.mapNotNull { (variable, implications) -> + val newImplications = if (to != null) { + implications.replaceAll { it.replaceVariable(from, to) } + } else { + implications.removeAll { it.effect.variable == from } + } + if (newImplications != implications) variable to newImplications else null + } + for ((variable, implications) in toReplace) { + if (implications.isEmpty()) { + remove(variable) + } else { + put(variable, implications) + } + } + if (existing != null && to != null) { + put(to, existing.replaceAll { it.replaceVariable(from, to) }) + } +} + +private inline fun PersistentList.replaceAll(block: (T) -> T): PersistentList = + mutate { result -> + val it = result.listIterator() + while (it.hasNext()) { + it.set(block(it.next())) + } + } + +private fun Implication.replaceVariable(from: RealVariable, to: RealVariable): Implication = when { + condition.variable == from -> Implication(condition.copy(variable = to), effect.replaceVariable(from, to)) + effect.variable == from -> Implication(condition, effect.replaceVariable(from, to)) + else -> this +} + +private fun Statement.replaceVariable(from: RealVariable, to: RealVariable): Statement = + if (variable != from) this else when (this) { + is OperationStatement -> copy(variable = to) + is PersistentTypeStatement -> copy(variable = to) + is MutableTypeStatement -> MutableTypeStatement(to, exactType) + } diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt deleted file mode 100644 index 4bbfe1fac48..00000000000 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/PersistentLogicSystem.kt +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.fir.resolve.dfa - -import kotlinx.collections.immutable.* -import org.jetbrains.kotlin.fir.types.ConeInferenceContext -import org.jetbrains.kotlin.types.AbstractTypeChecker -import java.util.* -import kotlin.math.max - -abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSystem(context) { - abstract val variableStorage: VariableStorageImpl - - override fun joinFlow(flows: Collection, union: Boolean): MutableFlow { - when (flows.size) { - 0 -> return MutableFlow() - 1 -> return flows.first().fork() - } - - val commonFlow = flows.reduce { a, b -> - // If you're debugging this assertion error, most likely cause is that a node is not - // marked as dead when all its input edges are dead. In that case it will have an empty flow, - // and joining that with a non-empty flow from another branch will fail. - a.lowestCommonAncestor(b) ?: error("no common ancestor in $a, $b") - } - val result = commonFlow.fork() - result.mergeAssignments(flows) - if (union) { - result.copyNonConflictingAliases(flows, commonFlow) - } else { - result.copyCommonAliases(flows) - } - result.copyStatements(flows, commonFlow, union) - // TODO: compute common implications? - return result - } - - private fun MutableFlow.mergeAssignments(flows: Collection) { - // If a variable was reassigned in one branch, it was reassigned at the join point. - val reassignedVariables = mutableMapOf() - for (flow in flows) { - for ((variable, index) in flow.assignmentIndex) { - if (assignmentIndex[variable] != index) { - // Ideally we should generate an entirely new index here, but it doesn't really - // matter; the important part is that it's different from `commonFlow.previousFlow`. - reassignedVariables[variable] = max(index, reassignedVariables[variable] ?: 0) - } - } - } - for ((variable, index) in reassignedVariables) { - recordNewAssignment(this, variable, index) - } - } - - private fun MutableFlow.copyCommonAliases(flows: Collection) { - for ((from, to) in flows.first().directAliasMap) { - // If `from -> to` is still in `this` (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 (directAliasMap[from] != to && flows.all { it.unwrapVariable(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(this, from, to) - } - } - } - - private fun MutableFlow.copyNonConflictingAliases(flows: Collection, commonFlow: PersistentFlow) { - val candidates = mutableMapOf() - for (flow in flows) { - for ((from, to) in flow.directAliasMap) { - candidates[from] = when { - // f({ a = b }, { notReassigning() }) -> a = b - commonFlow.assignmentIndex[from] == flow.assignmentIndex[from] -> continue - // f({ a = b }, { a = c }) -> a = b or c; can't express that, so just don't - from in candidates && candidates[from] != to -> null - // f({ a = b }, { a = b }) -> a = b - else -> to - } - } - } - for ((from, to) in candidates) { - addLocalVariableAlias(this, from, to ?: continue) - } - } - - private fun MutableFlow.copyStatements(flows: Collection, commonFlow: PersistentFlow, union: Boolean) { - flows.flatMapTo(mutableSetOf()) { it.knownVariables }.forEach computeStatement@{ variable -> - val statement = if (variable in directAliasMap) { - return@computeStatement // statements about alias == statements about aliased variable - } else if (!union) { - // if (condition) { /* x: S1 */ } else { /* x: S2 */ } // -> x: S1 | S2 - or(flows.mapTo(mutableSetOf()) { it.getTypeStatement(variable) ?: return@computeStatement }) - } else if (assignmentIndex[variable] == commonFlow.assignmentIndex[variable]) { - // 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() - } - } - } - - override fun addLocalVariableAlias(flow: MutableFlow, alias: RealVariable, underlyingVariable: RealVariable) { - if (underlyingVariable == alias) return // x = x - flow.directAliasMap[alias] = underlyingVariable - flow.backwardsAliasMap[underlyingVariable] = flow.backwardsAliasMap[underlyingVariable]?.add(alias) ?: persistentSetOf(alias) - } - - private fun MutableFlow.replaceVariable(variable: RealVariable, replacement: RealVariable?) { - val original = directAliasMap.remove(variable) - if (original != null) { - // All statements should've been made about whatever variable this is an alias to. There is nothing to replace. - if (AbstractTypeChecker.RUN_SLOW_ASSERTIONS) { - assert(variable !in backwardsAliasMap) - assert(variable !in logicStatements) - assert(variable !in approvedTypeStatements) - } - // `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. - val siblings = backwardsAliasMap.getValue(original) - if (siblings.size > 1) { - backwardsAliasMap[original] = siblings.remove(variable) - } else { - backwardsAliasMap.remove(original) - } - if (replacement != null) { - addLocalVariableAlias(this, replacement, original) - } - } else { - val aliases = backwardsAliasMap.remove(variable) - // If asked to remove the variable but there are aliases, replace with a new representative for the alias group instead. - val replacementOrNext = replacement ?: aliases?.first() - for (dependent in variable.dependentVariables) { - replaceVariable(dependent, replacementOrNext?.let { - variableStorage.copyRealVariableWithRemapping(dependent, variable, it) - }) - } - logicStatements.replaceVariable(variable, replacementOrNext) - approvedTypeStatements.replaceVariable(variable, replacementOrNext) - if (aliases != null && replacementOrNext != null) { - directAliasMap -= replacementOrNext - val withoutSelf = aliases - replacementOrNext - if (withoutSelf.isNotEmpty()) { - withoutSelf.associateWithTo(directAliasMap) { replacementOrNext } - backwardsAliasMap[replacementOrNext] = backwardsAliasMap[replacementOrNext]?.addAll(withoutSelf) ?: withoutSelf - } - } - } - } - - override fun addTypeStatement(flow: MutableFlow, statement: TypeStatement): TypeStatement? { - if (statement.exactType.isEmpty()) return null - val variable = statement.variable - val oldExactType = flow.approvedTypeStatements[variable]?.exactType - val newExactType = oldExactType?.addAll(statement.exactType) ?: statement.exactType.toPersistentSet() - if (newExactType === oldExactType) return null - return PersistentTypeStatement(variable, newExactType).also { flow.approvedTypeStatements[variable] = it } - } - - override fun addImplication(flow: MutableFlow, implication: Implication) { - 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[variable] = flow.logicStatements[variable]?.add(implication) ?: persistentListOf(implication) - } - - override fun translateVariableFromConditionInStatements( - flow: MutableFlow, - originalVariable: DataFlowVariable, - newVariable: DataFlowVariable, - transform: (Implication) -> Implication? - ) { - val statements = if (originalVariable.isSynthetic()) - flow.logicStatements.remove(originalVariable) - else - flow.logicStatements[originalVariable] - if (statements.isNullOrEmpty()) return - val existing = flow.logicStatements[newVariable] ?: persistentListOf() - flow.logicStatements[newVariable] = 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() - } - - private val nullableNothingType = context.session.builtinTypes.nullableNothingType.type - private val anyType = context.session.builtinTypes.anyType.type - - override fun approveOperationStatement(flow: PersistentFlow, statement: OperationStatement): TypeStatements = - approveOperationStatement(flow.logicStatements, statement, removeApprovedOrImpossible = false) - - override fun approveOperationStatement( - flow: MutableFlow, - statement: OperationStatement, - removeApprovedOrImpossible: Boolean, - ): TypeStatements = approveOperationStatement(flow.logicStatements, statement, removeApprovedOrImpossible) - - private fun approveOperationStatement( - logicStatements: Map>, - approvedStatement: OperationStatement, - removeApprovedOrImpossible: Boolean - ): TypeStatements { - val result = mutableMapOf() - val queue = LinkedList().apply { this += approvedStatement } - val approved = mutableSetOf() - while (queue.isNotEmpty()) { - val next = queue.removeFirst() - // Defense from cycles in facts - if (!removeApprovedOrImpossible && !approved.add(next)) continue - - val operation = next.operation - val variable = next.variable - if (variable.isReal()) { - val impliedType = if (operation == Operation.EqNull) nullableNothingType else anyType - result.getOrPut(variable) { MutableTypeStatement(variable) }.exactType.add(impliedType) - } - - val statements = logicStatements[variable] ?: continue - val stillUnknown = statements.removeAll { - val knownValue = it.condition.operation.valueIfKnown(operation) - if (knownValue == true) { - when (val effect = it.effect) { - is OperationStatement -> queue += effect - is TypeStatement -> - result.getOrPut(effect.variable) { MutableTypeStatement(effect.variable) } += effect - } - } - removeApprovedOrImpossible && knownValue != null - } - if (stillUnknown != statements && logicStatements is MutableMap) { - if (stillUnknown.isEmpty()) { - logicStatements.remove(variable) - } else { - logicStatements[variable] = stillUnknown - } - } - } - return result - } - - override fun recordNewAssignment(flow: MutableFlow, variable: RealVariable, index: Int) { - flow.replaceVariable(variable, null) - flow.assignmentIndex[variable] = index - } - - override fun isSameValueIn(a: PersistentFlow, b: MutableFlow, variable: RealVariable): Boolean = - a.assignmentIndex[variable] == b.assignmentIndex[variable] - - override fun isSameValueIn(a: PersistentFlow, b: PersistentFlow, variable: RealVariable): Boolean = - a.assignmentIndex[variable] == b.assignmentIndex[variable] -} - -private fun TypeStatement.toPersistent(): PersistentTypeStatement = when (this) { - is PersistentTypeStatement -> this - else -> PersistentTypeStatement(variable, exactType.toPersistentSet()) -} - -@JvmName("replaceVariableInStatements") -private fun MutableMap.replaceVariable(from: RealVariable, to: RealVariable?) { - val existing = remove(from) ?: return - if (to != null) { - put(to, existing.copy(variable = to)) - } -} - -@JvmName("replaceVariableInImplications") -private fun MutableMap>.replaceVariable(from: RealVariable, to: RealVariable?) { - val existing = remove(from) - val toReplace = entries.mapNotNull { (variable, implications) -> - val newImplications = if (to != null) { - implications.replaceAll { it.replaceVariable(from, to) } - } else { - implications.removeAll { it.effect.variable == from } - } - if (newImplications != implications) variable to newImplications else null - } - for ((variable, implications) in toReplace) { - if (implications.isEmpty()) { - remove(variable) - } else { - put(variable, implications) - } - } - if (existing != null && to != null) { - put(to, existing.replaceAll { it.replaceVariable(from, to) }) - } -} - -private inline fun PersistentList.replaceAll(block: (T) -> T): PersistentList = - mutate { result -> - val it = result.listIterator() - while (it.hasNext()) { - it.set(block(it.next())) - } - } - -private fun Implication.replaceVariable(from: RealVariable, to: RealVariable): Implication = when { - condition.variable == from -> Implication(condition.copy(variable = to), effect.replaceVariable(from, to)) - effect.variable == from -> Implication(condition, effect.replaceVariable(from, to)) - else -> this -} - -private fun Statement.replaceVariable(from: RealVariable, to: RealVariable): Statement = - if (variable != from) this else when (this) { - is OperationStatement -> copy(variable = to) - is PersistentTypeStatement -> copy(variable = to) - is MutableTypeStatement -> MutableTypeStatement(to, exactType) - }