From 88636c8dbfa33dc704626603c201b9f0fe912d81 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Mon, 6 Mar 2023 18:34:39 +0900 Subject: [PATCH] [LL API] Move modification tracker right inside a 'LLFirSession' --- .../level/api/fir/sessions/LLFirSession.kt | 45 +++++- .../sessions/LLFirSessionProviderStorage.kt | 142 ++++++------------ .../analysis/low/level/api/fir/util/utils.kt | 5 + 3 files changed, 91 insertions(+), 101 deletions(-) diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSession.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSession.kt index 12fcda17dcd..2b61f662ee2 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSession.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSession.kt @@ -6,13 +6,18 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.sessions import com.intellij.openapi.project.Project -import org.jetbrains.kotlin.analysis.project.structure.KtModule +import com.intellij.openapi.util.ModificationTracker +import org.jetbrains.kotlin.analysis.low.level.api.fir.util.BooleanModificationTracker +import org.jetbrains.kotlin.analysis.project.structure.* +import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory +import org.jetbrains.kotlin.analysis.utils.trackers.CompositeModificationTracker import org.jetbrains.kotlin.fir.BuiltinTypes import org.jetbrains.kotlin.fir.FirElementWithResolvePhase import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.PrivateSessionConstructor import org.jetbrains.kotlin.fir.resolve.ScopeSession import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol +import java.util.concurrent.atomic.AtomicBoolean @OptIn(PrivateSessionConstructor::class) abstract class LLFirSession( @@ -20,10 +25,46 @@ abstract class LLFirSession( override val builtinTypes: BuiltinTypes, kind: Kind ) : FirSession(sessionProvider = null, kind) { + abstract fun getScopeSession(): ScopeSession + + val modificationTracker: ModificationTracker + + private val initialModificationCount: Long + private val isExplicitlyInvalidated = AtomicBoolean(false) + val project: Project get() = ktModule.project - abstract fun getScopeSession(): ScopeSession + init { + val trackerFactory = KotlinModificationTrackerFactory.getService(ktModule.project) + val validityTracker = trackerFactory.createModuleStateTracker(ktModule) + + val outOfBlockTracker = when (ktModule) { + is KtSourceModule -> trackerFactory.createModuleWithoutDependenciesOutOfBlockModificationTracker(ktModule) + is KtNotUnderContentRootModule -> ModificationTracker { ktModule.file?.modificationStamp ?: 0 } + is KtScriptModule -> ModificationTracker { ktModule.file.modificationStamp } + is KtScriptDependencyModule -> ModificationTracker { ktModule.file?.modificationStamp ?: 0 } + else -> ModificationTracker.NEVER_CHANGED + } + + modificationTracker = CompositeModificationTracker.create( + listOf( + outOfBlockTracker, + ModificationTracker { validityTracker.rootModificationCount }, + BooleanModificationTracker { validityTracker.isValid }, + BooleanModificationTracker { !isExplicitlyInvalidated.get() } + ) + ) + + initialModificationCount = modificationTracker.modificationCount + } + + fun invalidate() { + isExplicitlyInvalidated.set(true) + } + + val isValid: Boolean + get() = modificationTracker.modificationCount == initialModificationCount } abstract class LLFirModuleSession( diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt index 769918486c2..97f1c81c2ec 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/LLFirSessionProviderStorage.kt @@ -6,16 +6,12 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.sessions import com.intellij.openapi.project.Project -import com.intellij.openapi.util.ModificationTracker import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirGlobalResolveComponents import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.LLFirBuiltinsSessionFactory import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.LLFirLibrarySessionFactory import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.llFirModuleData import org.jetbrains.kotlin.analysis.low.level.api.fir.util.addValueFor import org.jetbrains.kotlin.analysis.project.structure.* -import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory -import org.jetbrains.kotlin.analysis.providers.KtModuleStateTracker -import org.jetbrains.kotlin.analysis.utils.trackers.CompositeModificationTracker import java.lang.ref.SoftReference class LLFirSessionProviderStorage(val project: Project) { @@ -51,12 +47,11 @@ class LLFirSessionProviderStorage(val project: Project) { else -> error("Unexpected ${useSiteKtModule::class.simpleName}") } - private fun createSessionProviderForSourceSession( useSiteKtModule: KtSourceModule, configureSession: (LLFirSession.() -> Unit)? ): LLFirSessionProvider { - val (sessions, session) = sourceAsUseSiteSessionCache.withMappings(project) { mappings -> + val (sessions, session) = sourceAsUseSiteSessionCache.withMappings { mappings -> val sessions = mutableMapOf().apply { putAll(mappings) } val session = LLFirSessionFactory.createSourcesSession( project, @@ -77,7 +72,7 @@ class LLFirSessionProviderStorage(val project: Project) { useSiteKtModule: KtModule, configureSession: (LLFirSession.() -> Unit)? ): LLFirSessionProvider { - val (sessions, session) = libraryAsUseSiteSessionCache.withMappings(project) { mappings -> + val (sessions, session) = libraryAsUseSiteSessionCache.withMappings { mappings -> val sessions = mutableMapOf().apply { putAll(mappings) } val session = LLFirSessionFactory.createLibraryOrLibrarySourceResolvableSession( project, @@ -97,7 +92,7 @@ class LLFirSessionProviderStorage(val project: Project) { useSiteKtModule: KtScriptModule, configureSession: (LLFirSession.() -> Unit)? ): LLFirSessionProvider { - val (sessions, session) = sourceAsUseSiteSessionCache.withMappings(project) { mappings -> + val (sessions, session) = sourceAsUseSiteSessionCache.withMappings { mappings -> val sessions = mutableMapOf().apply { putAll(mappings) } val session = LLFirSessionFactory.createScriptSession( project, @@ -116,7 +111,7 @@ class LLFirSessionProviderStorage(val project: Project) { useSiteKtModule: KtNotUnderContentRootModule, configureSession: (LLFirSession.() -> Unit)? ): LLFirSessionProvider { - val (sessions, session) = notUnderContentRootSessionCache.withMappings(project) { mappings -> + val (sessions, session) = notUnderContentRootSessionCache.withMappings { mappings -> val sessions = mutableMapOf().apply { putAll(mappings) } val session = LLFirSessionFactory.createNotUnderContentRootResolvableSession( project, @@ -133,111 +128,60 @@ class LLFirSessionProviderStorage(val project: Project) { private class LLFirSessionsCache { @Volatile - private var mappings: Map = emptyMap() + private var mappings: Map> = emptyMap() val sessionInvalidator: LLFirSessionInvalidator = LLFirSessionInvalidator { session -> - mappings[session.llFirModuleData.ktModule]?.invalidate() + mappings[session.llFirModuleData.ktModule]?.get()?.invalidate() } - inline fun withMappings( - project: Project, action: (Map) -> Pair, R> ): Pair, R> { val (newMappings, result) = action(getSessions().mapValues { it.value }) - mappings = newMappings.mapValues { FirSessionWithModificationTracker(project, it.value) } + mappings = newMappings.mapValues { SoftReference(it.value) } return newMappings to result } private fun getSessions(): Map = buildMap { - val sessions = mappings.values - val wasSessionInvalidated = sessions.associateWithTo(hashMapOf()) { false } - - val reversedDependencies = sessions.reversedDependencies { session -> - if (session.validityTracker.isValid) { - session.ktModule.directRegularDependencies.mapNotNull { mappings[it] } - } else emptyList() - } - - fun markAsInvalidWithDfs(session: FirSessionWithModificationTracker) { - if (wasSessionInvalidated.getValue(session)) { - // we already was in that branch - return - } - wasSessionInvalidated[session] = true - reversedDependencies[session]?.forEach { dependsOn -> - markAsInvalidWithDfs(dependsOn) + // Initially, all sessions are considered to be valid ('true'). + val sessions = LinkedHashMap().apply { + for (sessionRef in mappings.values) { + val session = sessionRef.get() ?: continue + put(session, true) } } - for (session in sessions) { - if (!session.isValid) { - markAsInvalidWithDfs(session) - } - } - return wasSessionInvalidated.entries - .mapNotNull { (sessionWithTracker, wasInvalidated) -> - if (wasInvalidated) return@mapNotNull null - val firSession = sessionWithTracker.firSessionSoftReference.get() ?: return@mapNotNull null - sessionWithTracker.ktModule to firSession - }.toMap() - } - - private fun Collection.reversedDependencies(getDependencies: (T) -> List): Map> { - val result = hashMapOf>() - forEach { from -> - getDependencies(from).forEach { to -> - result.addValueFor(to, from) - } - } - return result - } -} - -private class FirSessionWithModificationTracker( - project: Project, - firSession: LLFirSession, -) { - val firSessionSoftReference: SoftReference = SoftReference(firSession) - val ktModule = firSession.llFirModuleData.ktModule - - val validityTracker: KtModuleStateTracker - private val modificationTracker: ModificationTracker - - init { - val trackerFactory = KotlinModificationTrackerFactory.getService(project) - - validityTracker = trackerFactory.createModuleStateTracker(ktModule) - - val outOfBlockTracker = when (ktModule) { - is KtSourceModule -> trackerFactory.createModuleWithoutDependenciesOutOfBlockModificationTracker(ktModule) - is KtNotUnderContentRootModule -> ModificationTracker { ktModule.file?.modificationStamp ?: 0 } - is KtScriptModule -> ModificationTracker { ktModule.file.modificationStamp } - is KtScriptDependencyModule -> ModificationTracker { ktModule.file?.modificationStamp ?: 0 } - else -> null - } - modificationTracker = CompositeModificationTracker.create( - listOfNotNull( - outOfBlockTracker, - object : ModificationTracker { - override fun getModificationCount() = validityTracker.rootModificationCount + val reversedDependencies = buildMap { + for (session in sessions.keys) { + if (session.isValid) { + val module = session.ktModule + for (dependency in module.directRegularDependencies) { + addValueFor(dependency, module) + } } - ) - ) + } + } + + fun invalidateRecursively(session: LLFirSession) { + // Invalidate all dependent sessions only if we didn't that before + if (sessions.put(session, false) == true) { + for (dependentModule in reversedDependencies[session.ktModule].orEmpty()) { + val dependentSession = mappings[dependentModule]?.get() ?: continue + invalidateRecursively(dependentSession) + } + } + } + + for (session in sessions.keys) { + if (session.isValid) continue + invalidateRecursively(session) + } + + return buildMap { + for ((session, isValid) in sessions) { + if (!isValid) continue + put(session.ktModule, session) + } + } } - - - private val timeStamp = modificationTracker.modificationCount - - @Volatile - private var isInvalidated = false - - fun invalidate() { - isInvalidated = true - } - - val isValid: Boolean - get() = validityTracker.isValid - && !isInvalidated - && modificationTracker.modificationCount == timeStamp } \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/utils.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/utils.kt index a85e218db2d..3fafee4aa5c 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/utils.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/util/utils.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.util import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.util.ModificationTracker import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.diagnostics.FirDiagnosticHolder @@ -65,3 +66,7 @@ internal fun KtDeclaration.isNonAnonymousClassOrObject() = this is KtClassOrObject && !this.isObjectLiteral() + +internal fun BooleanModificationTracker(provider: () -> Boolean): ModificationTracker { + return ModificationTracker { if (provider()) 0 else 1 } +} \ No newline at end of file