Skip to content

Commit ddcfa15

Browse files
committed
[DFA] Move base property stability cache to flow
This is an exploritory change to move the lazy PropertyStability value from a RealVariable into a Flow. This makes RealVariable stateless, so maintaining the same instance is no longer a performance optimization, only a memory optimization (something we can tackle again later). ^KT-87944
1 parent bf8d49a commit ddcfa15

7 files changed

Lines changed: 257 additions & 229 deletions

File tree

analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/ContextCollector.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ private class ContextCollectorVisitor(
347347
}
348348

349349
if (flow != null) {
350-
val realVariables = flow.knownVariables.filterIsInstance<RealVariable>()
350+
val realVariables = flow.realVariables.keys.filterIsInstance<RealVariable>()
351351
.sortedBy { it.symbol.memberDeclarationNameOrNull?.asString() }
352352

353353
for (realVariable in realVariables) {
@@ -409,7 +409,7 @@ private class ContextCollectorVisitor(
409409

410410
@OptIn(CfgInternals::class)
411411
private fun computeExpressionStability(fir: FirExpression, flow: Flow): SmartcastStability? {
412-
val realVariable = flow.getVariable(fir) as? RealVariable ?: return null
412+
val realVariable = flow.getVariableIfKnown(fir) as? RealVariable ?: return null
413413
val targetTypes = flow.getTypeStatement(realVariable)?.upperTypes
414414

415415
return context(bodyHolder, context.dataFlowAnalyzerContext) {

compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class DataFlowAnalyzerContext private constructor(
127127
@CfgInternals
128128
context(holder: SessionHolder, context: DataFlowAnalyzerContext)
129129
fun RealVariable.computeEffectiveStability(flow: Flow, targetTypes: Set<ConeKotlinType>?): SmartcastStability {
130-
val stability = getStability(flow, holder.session)
130+
val stability = flow.getStability(this)
131131

132132
if (stability == SmartcastStability.CAPTURED_VARIABLE) {
133133
if (!isUnstableLocalVariable(targetTypes)) {
@@ -1088,6 +1088,7 @@ abstract class FirDataFlowAnalyzer(
10881088

10891089
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
10901090
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow { _, flow ->
1091+
flow.remember(qualifiedAccessExpression)
10911092
processConditionalContract(flow, qualifiedAccessExpression, callArgsExit = null)
10921093
processBackingFieldAccess(flow, qualifiedAccessExpression)
10931094
processEqualsParameterAccess(flow, qualifiedAccessExpression)
@@ -1497,7 +1498,7 @@ abstract class FirDataFlowAnalyzer(
14971498
}
14981499
var needToAddInitializerStatement = isAssignment
14991500

1500-
val stability = propertyVariable.getStability(flow, components.session)
1501+
val stability = flow.getStability(propertyVariable)
15011502
if (stability == SmartcastStability.STABLE_VALUE || stability == SmartcastStability.CAPTURED_VARIABLE) {
15021503
val initializerVariable = flow.rememberVariableIfUsedOrReal(initializer)
15031504
if (!hasExplicitType && initializerVariable is RealVariable &&

compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/DfaVariables.kt

Lines changed: 0 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -100,31 +100,6 @@ sealed class DataFlowVariable {
100100
}
101101
}
102102

103-
private enum class PropertyStability(
104-
val inherentInstability: SmartcastStability?,
105-
val checkModule: Boolean = false,
106-
val checkReceiver: Boolean = false,
107-
) {
108-
// Private vals can only be accessed from the same scope, so they're always safe to smart cast.
109-
// Constant values (e.g. singleton objects) cannot be reassigned no matter what, so they're always safe
110-
// to smart cast as well, although this is not very useful.
111-
PRIVATE_OR_CONST_VAL(null),
112-
113-
// Public final vals can be accessed from different modules, which are not necessarily recompiled
114-
// when the module declaring the property changes, so smart casting them there is unsafe.
115-
PUBLIC_FINAL_VAL(null, checkModule = true),
116-
117-
// Public open vals can be overridden with custom getters, so smart casting them is only safe
118-
// if the receiver is known to be of a final type that doesn't do that.
119-
PUBLIC_OPEN_VAL(null, checkModule = true, checkReceiver = true),
120-
121-
CAPTURED_VARIABLE(SmartcastStability.CAPTURED_VARIABLE),
122-
EXPECT_PROPERTY(SmartcastStability.EXPECT_PROPERTY),
123-
PROPERTY_WITH_GETTER(SmartcastStability.PROPERTY_WITH_GETTER),
124-
MUTABLE_PROPERTY(SmartcastStability.MUTABLE_PROPERTY),
125-
DELEGATED_PROPERTY(SmartcastStability.DELEGATED_PROPERTY);
126-
}
127-
128103
class RealVariable(
129104
val symbol: FirBasedSymbol<*>,
130105
val isImplicit: Boolean,
@@ -165,95 +140,12 @@ class RealVariable(
165140
append("(${dispatchReceiver ?: extensionReceiver})")
166141
}
167142
}
168-
169-
fun getStability(flow: Flow, session: FirSession): SmartcastStability {
170-
if (!isImplicit) {
171-
val stability = propertyStability
172-
173-
val isUnstableSmartcastOnDelegatedProperties =
174-
session.languageVersionSettings.supportsFeature(LanguageFeature.UnstableSmartcastOnDelegatedProperties)
175-
if (isUnstableSmartcastOnDelegatedProperties && (symbol.fir as? FirProperty)?.isDelegated == true) return SmartcastStability.DELEGATED_PROPERTY
176-
177-
stability.inherentInstability?.let { return it }
178-
if (symbol is FirPropertySymbol && symbol.fir.isImplicitWhenSubjectVariable) {
179-
flow.unwrapVariable(this).takeIf { it != this }?.let { return it.getStability(flow, session) }
180-
}
181-
if (stability.checkReceiver && dispatchReceiver?.hasFinalType(flow, session) == false)
182-
return SmartcastStability.PROPERTY_WITH_GETTER
183-
if (stability.checkModule && !(symbol.fir as FirVariable).isInCurrentOrFriendModule(session))
184-
return SmartcastStability.ALIEN_PUBLIC_PROPERTY
185-
// Members of unstable values should always be unstable, as the receiver could've changed.
186-
dispatchReceiver?.getStability(flow, session)?.takeIf { it != SmartcastStability.STABLE_VALUE }?.let { return it }
187-
// No need to check extension receiver, as properties with one cannot be stable by symbol stability.
188-
}
189-
return SmartcastStability.STABLE_VALUE
190-
}
191-
192-
private fun hasFinalType(flow: Flow, session: FirSession): Boolean =
193-
originalType.isFinal(session) || flow.getTypeStatement(this)?.upperTypes?.any { it.isFinal(session) } == true
194-
195-
private val propertyStability: PropertyStability by lazy {
196-
when (val fir = symbol.fir) {
197-
!is FirVariable -> PropertyStability.PRIVATE_OR_CONST_VAL // named object or containing class for a static field reference
198-
is FirEnumEntry -> PropertyStability.PRIVATE_OR_CONST_VAL
199-
is FirErrorProperty -> PropertyStability.PRIVATE_OR_CONST_VAL
200-
is FirValueParameter -> PropertyStability.PRIVATE_OR_CONST_VAL
201-
is FirBackingField -> when {
202-
fir.isVal -> PropertyStability.PRIVATE_OR_CONST_VAL
203-
else -> PropertyStability.MUTABLE_PROPERTY
204-
}
205-
is FirField -> when {
206-
fir.isVal -> PropertyStability.PUBLIC_FINAL_VAL
207-
else -> PropertyStability.MUTABLE_PROPERTY
208-
}
209-
is FirProperty -> when {
210-
fir.isExpect -> PropertyStability.EXPECT_PROPERTY
211-
fir.delegate != null -> PropertyStability.DELEGATED_PROPERTY
212-
// Local vars are only *sometimes* unstable (when there are concurrent assignments). `FirDataFlowAnalyzer`
213-
// will check that at each use site individually and mark the access as stable when possible.
214-
fir.symbol is FirLocalPropertySymbol -> when {
215-
fir.isVal -> PropertyStability.PRIVATE_OR_CONST_VAL
216-
else -> PropertyStability.CAPTURED_VARIABLE
217-
}
218-
fir.isVar -> PropertyStability.MUTABLE_PROPERTY
219-
fir.isInstanceExtension -> PropertyStability.PROPERTY_WITH_GETTER
220-
fir.getter !is FirDefaultPropertyAccessor? -> PropertyStability.PROPERTY_WITH_GETTER
221-
fir.visibility == Visibilities.Private -> PropertyStability.PRIVATE_OR_CONST_VAL
222-
// REPL vals can be treated the same as local vals.
223-
// TODO(???): allow REPL vars to be treated as local vars.
224-
fir.isReplSnippetDeclaration == true -> PropertyStability.PRIVATE_OR_CONST_VAL
225-
fir.isFinal -> PropertyStability.PUBLIC_FINAL_VAL
226-
else -> PropertyStability.PUBLIC_OPEN_VAL
227-
}
228-
}
229-
}
230143
}
231144

232145
data class SyntheticVariable(val fir: FirExpression) : DataFlowVariable() {
233146
override val originalType: ConeKotlinType get() = fir.resolvedType
234147
}
235148

236-
private fun ConeKotlinType.isFinal(session: FirSession): Boolean = when (this) {
237-
is ConeFlexibleType -> lowerBound.isFinal(session)
238-
is ConeDefinitelyNotNullType -> original.isFinal(session)
239-
is ConeClassLikeType -> toSymbol(session)?.fullyExpandedClass(session)?.isFinal == true
240-
is ConeTypeParameterType -> toTypeParameterSymbol(session)?.resolvedBounds?.any { it.coneType.isFinal(session) } == true
241-
242-
is ConeIntersectionType -> intersectedTypes.any { it.isFinal(session) }
243-
is ConeCapturedType -> constructor.supertypes?.any { it.isFinal(session) } == true
244-
is ConeIntegerLiteralType -> true
245-
246-
is ConeStubType,
247-
is ConeTypeVariableType,
248-
-> false
249-
}
250-
251-
private fun FirVariable.isInCurrentOrFriendModule(session: FirSession): Boolean {
252-
val propertyModuleData = originalOrSelf().moduleData
253-
val currentModuleData = session.moduleData
254-
return currentModuleData.canSeeInternalsOf(propertyModuleData)
255-
}
256-
257149
private tailrec fun FirExpression.unwrapElement(): FirExpression? {
258150
return when (this) {
259151
is FirSmartCastExpression -> originalExpression.unwrapElement()

0 commit comments

Comments
 (0)