FIR DFA: reduce the number of redundant statements created

If a variable does not yet exist, then it should only be created if it
is a real variable and the implication or statement we're about to add
is about its type or nullability (which is also about the type).
Otherwise, the statement/implication will have no effect because there
are currently no implications referencing that variable, and if it's
synthetic then there will never be because we won't visit it again.

Hopefully this reduces the performance impact of flow forks.
This commit is contained in:
pyos
2022-11-11 21:32:48 +01:00
committed by teamcity
parent ae31275f73
commit 49f8de50c3
5 changed files with 232 additions and 293 deletions
@@ -10,8 +10,6 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
abstract class VariableStorage {
abstract fun getRealVariableWithoutUnwrappingAlias(flow: Flow, fir: FirElement): RealVariable?
abstract fun getRealVariable(flow: Flow, fir: FirElement): RealVariable?
abstract fun getLocalVariable(symbol: FirBasedSymbol<*>): RealVariable?
abstract fun getSyntheticVariable(fir: FirElement): SyntheticVariable?
abstract fun getVariable(flow: Flow, fir: FirElement): DataFlowVariable?
abstract fun get(flow: Flow, fir: FirElement): DataFlowVariable?
}
@@ -25,8 +25,6 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@OptIn(DfaInternals::class)
class VariableStorageImpl(private val session: FirSession) : VariableStorage() {
@@ -47,40 +45,63 @@ class VariableStorageImpl(private val session: FirSession) : VariableStorage() {
): RealVariable {
val realFir = fir.unwrapElement()
val identifier = getIdentifierBySymbol(flow, symbol, realFir)
return _realVariables.getOrPut(identifier) { createRealVariableInternal(flow, identifier, realFir, stability) }
return _realVariables[identifier] ?: createReal(flow, identifier, realFir, stability)
}
private fun getOrCreateRealVariable(
flow: Flow,
symbol: FirBasedSymbol<*>,
fir: FirElement,
stability: PropertyStability
): RealVariable {
return flow.unwrapVariable(getOrCreateRealVariableWithoutUnwrappingAlias(flow, symbol, fir, stability))
override fun getRealVariableWithoutUnwrappingAlias(flow: Flow, fir: FirElement): RealVariable? {
val realFir = fir.unwrapElement()
val symbol = realFir.symbol ?: return null
if (symbol.getStability(realFir) == null) return null
return _realVariables[getIdentifierBySymbol(flow, symbol, realFir)]
}
private fun getIdentifierBySymbol(
flow: Flow,
symbol: FirBasedSymbol<*>,
fir: FirElement,
): Identifier {
override fun getLocalVariable(symbol: FirBasedSymbol<*>): RealVariable? =
_realVariables[Identifier(symbol, null, null)]
// General pattern when using these function:
//
// val argumentVariable = variableStorage.{get,getOrCreateIfReal}(flow, fir.argument) ?: return
// val expressionVariable = variableStorage.createSynthetic(fir)
// flow.addImplication(somethingAbout(expressionVariable) implies somethingElseAbout(argumentVariable))
//
// If "something else" is a type/nullability statement, use `getOrCreateIfReal`; if it's `... == true/false`, use `get`.
// The point is to only create variables and statements if they lead to useful conclusions; if a variable
// does not exist, then no statements about it have been made, and if it's synthetic, none will be created later.
override fun get(flow: Flow, fir: FirElement): DataFlowVariable? =
get(flow, fir.unwrapElement(), createReal = false)
fun getOrCreateIfReal(flow: Flow, fir: FirElement): DataFlowVariable? =
get(flow, fir.unwrapElement(), createReal = true)
fun getOrCreate(flow: Flow, fir: FirElement): DataFlowVariable =
fir.unwrapElement().let { get(flow, it, createReal = true) ?: createSynthetic(it) }
fun createSynthetic(fir: FirElement): SyntheticVariable =
SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it }
private fun get(flow: Flow, realFir: FirElement, createReal: Boolean): DataFlowVariable? {
val symbol = realFir.symbol
val stability = symbol?.getStability(realFir) ?: return syntheticVariables[realFir]
val identifier = getIdentifierBySymbol(flow, symbol, realFir)
return _realVariables[identifier]?.let(flow::unwrapVariable)
?: if (createReal) createReal(flow, identifier, realFir, stability) else null
}
fun removeRealVariable(symbol: FirBasedSymbol<*>) {
_realVariables.remove(Identifier(symbol, null, null))
}
private fun getIdentifierBySymbol(flow: Flow, symbol: FirBasedSymbol<*>, fir: FirElement): Identifier {
val expression = fir as? FirQualifiedAccess
// TODO: don't create receiver variables if not going to create the composed variable either?
return Identifier(
symbol,
expression?.dispatchReceiver?.takeIf { it != FirNoReceiverExpression }?.let { getOrCreateVariable(flow, it) },
expression?.extensionReceiver?.takeIf { it != FirNoReceiverExpression }?.let { getOrCreateVariable(flow, it) }
expression?.dispatchReceiver?.takeIf { it != FirNoReceiverExpression }?.let { getOrCreate(flow, it) },
expression?.extensionReceiver?.takeIf { it != FirNoReceiverExpression }?.let { getOrCreate(flow, it) }
)
}
/**
* [originalFir] used for extracting expression under <when_subject> and extracting receiver
*/
private fun createRealVariableInternal(
flow: Flow,
identifier: Identifier,
originalFir: FirElement,
stability: PropertyStability
): RealVariable {
private fun createReal(flow: Flow, identifier: Identifier, originalFir: FirElement, stability: PropertyStability): RealVariable {
val receiver: FirExpression?
val isThisReference: Boolean
val expression: FirQualifiedAccess? = when (originalFir) {
@@ -98,8 +119,10 @@ class VariableStorageImpl(private val session: FirSession) : VariableStorage() {
isThisReference = false
}
val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) }
return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability)
val receiverVariable = receiver?.let { getOrCreate(flow, it) }
return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability).also {
_realVariables[identifier] = it
}
}
fun copyRealVariableWithRemapping(variable: RealVariable, from: RealVariable, to: RealVariable): RealVariable {
@@ -119,69 +142,12 @@ class VariableStorageImpl(private val session: FirSession) : VariableStorage() {
}
}
fun getOrCreateRealVariable(flow: Flow, fir: FirElement): RealVariable? {
val realFir = fir.unwrapElement()
val symbol = realFir.symbol ?: return null
val stability = symbol.getStability(realFir) ?: return null
return getOrCreateRealVariable(flow, symbol, realFir, stability)
}
fun createSyntheticVariable(fir: FirElement): SyntheticVariable =
SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it }
fun getOrCreateVariable(flow: Flow, fir: FirElement): DataFlowVariable {
val realFir = fir.unwrapElement()
val symbol = realFir.symbol
val stability = symbol.getStability(realFir)
return if (stability != null) {
getOrCreateRealVariable(flow, symbol!!, realFir, stability)
} else {
syntheticVariables[realFir] ?: createSyntheticVariable(realFir)
}
}
override fun getRealVariableWithoutUnwrappingAlias(flow: Flow, fir: FirElement): RealVariable? {
val realFir = fir.unwrapElement()
val symbol = realFir.symbol ?: return null
if (symbol.getStability(realFir) == null) return null
return _realVariables[getIdentifierBySymbol(flow, symbol, realFir)]
}
override fun getRealVariable(flow: Flow, fir: FirElement): RealVariable? {
return getRealVariableWithoutUnwrappingAlias(flow, fir)?.let { flow.unwrapVariable(it) }
}
override fun getLocalVariable(symbol: FirBasedSymbol<*>): RealVariable? =
_realVariables[Identifier(symbol, null, null)]
override fun getSyntheticVariable(fir: FirElement): SyntheticVariable? {
return syntheticVariables[fir.unwrapElement()]
}
override fun getVariable(flow: Flow, fir: FirElement): DataFlowVariable? {
return getRealVariable(flow, fir) ?: getSyntheticVariable(fir)
}
fun removeRealVariable(symbol: FirBasedSymbol<*>) {
_realVariables.remove(Identifier(symbol, null, null))
}
fun removeSyntheticVariable(variable: DataFlowVariable) {
if (!variable.isSynthetic()) return
syntheticVariables.remove(variable.fir)
}
@OptIn(ExperimentalContracts::class)
fun FirBasedSymbol<*>?.getStability(originalFir: FirElement): PropertyStability? {
contract {
returnsNotNull() implies (this@getStability != null)
}
private fun FirBasedSymbol<*>.getStability(originalFir: FirElement): PropertyStability? {
when (this) {
is FirAnonymousObjectSymbol -> return null
is FirFunctionSymbol<*>,
is FirClassSymbol<*>,
is FirBackingFieldSymbol -> return PropertyStability.STABLE_VALUE
null -> return null
}
if (originalFir is FirThisReceiverExpression) return PropertyStability.STABLE_VALUE
if (this !is FirVariableSymbol<*>) return null