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:
@@ -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?
|
||||
}
|
||||
|
||||
+52
-86
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user