From dd9a96a12f8b47ab6c2140307265da4f7713111d Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Thu, 4 Nov 2021 15:40:46 +0100 Subject: [PATCH] FIR LC: fix light facade caching --- .../analysis-internal-utils/build.gradle.kts | 18 +++++++ .../analysis/utils/caches/softCachedValue.kt | 23 +++++++++ .../utils/caches/strongCachedValue.kt | 49 +++++++++++++++++++ analysis/low-level-api-fir/build.gradle.kts | 1 + .../api/fir/FirIdeResolveStateService.kt | 6 +-- .../sessions/FirIdeSessionProviderStorage.kt | 4 +- .../analysis/low/level/api/fir/util/utils.kt | 19 ------- .../api/fir/compiler/based/testServices.kt | 2 + .../symbol-light-classes/build.gradle.kts | 1 + .../symbol/IDEKotlinAsJavaFirSupport.kt | 11 +++-- .../caches/SymbolLightClassFacadeCache.kt | 44 +++++++++++++++++ .../symbol/classes/firLightClassUtils.kt | 22 +-------- .../build.gradle.kts | 8 ++- settings.gradle | 1 + 14 files changed, 159 insertions(+), 50 deletions(-) create mode 100644 analysis/analysis-internal-utils/build.gradle.kts create mode 100644 analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/softCachedValue.kt create mode 100644 analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/strongCachedValue.kt create mode 100644 analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/caches/SymbolLightClassFacadeCache.kt diff --git a/analysis/analysis-internal-utils/build.gradle.kts b/analysis/analysis-internal-utils/build.gradle.kts new file mode 100644 index 00000000000..98661c85300 --- /dev/null +++ b/analysis/analysis-internal-utils/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +dependencies { + implementation(project(":compiler:psi")) + implementation(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) } +} + +sourceSets { + "main" { projectDefault() } + "test" { none() } +} + +kotlin { + explicitApi() +} \ No newline at end of file diff --git a/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/softCachedValue.kt b/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/softCachedValue.kt new file mode 100644 index 00000000000..ecf87790154 --- /dev/null +++ b/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/softCachedValue.kt @@ -0,0 +1,23 @@ +package org.jetbrains.kotlin.analysis.utils.caches + +import com.intellij.openapi.project.Project +import com.intellij.psi.util.CachedValue +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import kotlin.reflect.KProperty + + +@Suppress("NOTHING_TO_INLINE") +public inline operator fun CachedValue.getValue(thisRef: Any?, property: KProperty<*>): T = value + +public inline fun softCachedValue( + project: Project, + vararg dependencies: Any, + crossinline createValue: () -> T +): CachedValue = + CachedValuesManager.getManager(project).createCachedValue { + CachedValueProvider.Result( + createValue(), + dependencies + ) + } diff --git a/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/strongCachedValue.kt b/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/strongCachedValue.kt new file mode 100644 index 00000000000..378645bc8e2 --- /dev/null +++ b/analysis/analysis-internal-utils/src/org/jetbrains/kotlin/analysis/utils/caches/strongCachedValue.kt @@ -0,0 +1,49 @@ +/* + * 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.analysis.utils.caches + +import com.intellij.openapi.util.ModificationTracker +import java.util.concurrent.atomic.AtomicReference +import kotlin.reflect.KProperty + +public class StrongRefModificationTrackerBasedCache internal constructor( + private val dependencies: List, + private val compute: () -> T, +) { + private val cached = AtomicReference?>(null) + + public operator fun getValue(thisRef: Any?, property: KProperty<*>): T = cached.updateAndGet { value -> + when { + value == null -> createNewCachedValue() + value.isUpToDate(dependencies) -> value + else -> createNewCachedValue() + } + }!!.value + + private fun createNewCachedValue() = CachedValue(compute(), dependencies.map { it.modificationCount }) +} + +private class CachedValue(val value: T, val timestamps: List) { + fun isUpToDate(dependencies: List): Boolean { + check(timestamps.size == dependencies.size) + for (i in timestamps.indices) { + if (dependencies[i].modificationCount != timestamps[i]) { + return false + } + } + return true + } +} + +/** + * Create modification tracker which will be invalidated when dependencies change. + * The cached value is hold on the strong reference. + * So, the value will not be garbage collected until modification tracker changes. + */ +public fun strongCachedValue( + vararg dependencies: ModificationTracker, + compute: () -> T, +): StrongRefModificationTrackerBasedCache = StrongRefModificationTrackerBasedCache(dependencies.toList(), compute) \ No newline at end of file diff --git a/analysis/low-level-api-fir/build.gradle.kts b/analysis/low-level-api-fir/build.gradle.kts index e840b188378..a1b4e310b3d 100644 --- a/analysis/low-level-api-fir/build.gradle.kts +++ b/analysis/low-level-api-fir/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(project(":compiler:ir.psi2ir")) implementation(project(":compiler:fir:entrypoint")) implementation(project(":analysis:analysis-api-providers")) + implementation(project(":analysis:analysis-internal-utils")) api(intellijCoreDep()) { includeJars("intellij-core", "guava", rootProject = rootProject) } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/FirIdeResolveStateService.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/FirIdeResolveStateService.kt index 228987a7e7d..7f8ad3a5cf6 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/FirIdeResolveStateService.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/FirIdeResolveStateService.kt @@ -13,18 +13,16 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirModuleResolveState import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.FirLazyDeclarationResolver import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.FirIdeSession import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.FirIdeSessionProviderStorage -import org.jetbrains.kotlin.analysis.low.level.api.fir.util.cachedValue -import org.jetbrains.kotlin.analysis.low.level.api.fir.util.getValue import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker +import org.jetbrains.kotlin.analysis.utils.caches.strongCachedValue import java.util.concurrent.ConcurrentHashMap internal class FirIdeResolveStateService(project: Project) { private val sessionProviderStorage = FirIdeSessionProviderStorage(project) - private val stateCache by cachedValue( - project, + private val stateCache by strongCachedValue( project.createProjectWideOutOfBlockModificationTracker(), ProjectRootModificationTracker.getInstance(project), ) { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/FirIdeSessionProviderStorage.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/FirIdeSessionProviderStorage.kt index 9d710313885..80ba26ea11f 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/FirIdeSessionProviderStorage.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/sessions/FirIdeSessionProviderStorage.kt @@ -16,16 +16,16 @@ import org.jetbrains.kotlin.fir.FirModuleData import org.jetbrains.kotlin.fir.moduleData import org.jetbrains.kotlin.analysis.low.level.api.fir.FirPhaseRunner import org.jetbrains.kotlin.analysis.low.level.api.fir.util.addValueFor -import org.jetbrains.kotlin.analysis.low.level.api.fir.util.* import org.jetbrains.kotlin.analysis.low.level.api.fir.util.executeWithoutPCE import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.project.structure.KtSourceModule +import org.jetbrains.kotlin.analysis.utils.caches.* import java.util.concurrent.ConcurrentHashMap class FirIdeSessionProviderStorage(val project: Project) { private val sessionsCache = ConcurrentHashMap() - private val librariesCache by cachedValue(project, project.createLibrariesModificationTracker()) { LibrariesCache() } + private val librariesCache by softCachedValue(project, project.createLibrariesModificationTracker()) { LibrariesCache() } fun getSessionProvider( rootModule: KtSourceModule, 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 58bc878b66a..be516212dd7 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 @@ -95,25 +95,6 @@ internal fun KtDeclaration.isNonAnonymousClassOrObject() = && this !is KtEnumEntry -@Suppress("NOTHING_TO_INLINE") -internal inline operator fun CachedValue.getValue(thisRef: Any?, property: KProperty<*>): T = value - -internal inline fun cachedValue(project: Project, vararg dependencies: Any, crossinline createValue: () -> T) = - CachedValuesManager.getManager(project).createCachedValue { - CachedValueProvider.Result( - createValue(), - dependencies - ) - } - -/** - * Creates a value which will be cached until until any physical PSI change happens - * - * @see com.intellij.psi.util.CachedValue - * @see com.intellij.psi.util.PsiModificationTracker.MODIFICATION_COUNT - */ -internal fun psiModificationTrackerBasedCachedValue(project: Project, createValue: () -> T) = - cachedValue(project, PsiModificationTracker.MODIFICATION_COUNT, createValue = createValue) fun KtElement.getElementTextInContext(): String { val context = parentOfType() diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/compiler/based/testServices.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/compiler/based/testServices.kt index 9a4adb4d48b..c3ed17b2342 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/compiler/based/testServices.kt +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/compiler/based/testServices.kt @@ -32,6 +32,7 @@ import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport import org.jetbrains.kotlin.asJava.finder.JavaElementFinder import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider import org.jetbrains.kotlin.light.classes.symbol.IDEKotlinAsJavaFirSupport +import org.jetbrains.kotlin.light.classes.symbol.caches.SymbolLightClassFacadeCache import org.jetbrains.kotlin.load.kotlin.PackagePartProvider import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.model.TestModule @@ -60,6 +61,7 @@ fun MockProject.registerTestServices( registerService(KotlinDeclarationProviderFactory::class.java, KotlinStaticDeclarationProviderFactory(allKtFiles)) registerService(KotlinPackageProviderFactory::class.java, KotlinStaticPackageProviderFactory(allKtFiles)) registerService(ProjectStructureProvider::class.java, KotlinProjectStructureProviderTestImpl(testServices)) + registerService(SymbolLightClassFacadeCache::class.java) reRegisterJavaElementFinder(this) } diff --git a/analysis/symbol-light-classes/build.gradle.kts b/analysis/symbol-light-classes/build.gradle.kts index f772709a547..07622a02e08 100644 --- a/analysis/symbol-light-classes/build.gradle.kts +++ b/analysis/symbol-light-classes/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation(project(":compiler:light-classes")) implementation(project(":analysis:analysis-api-providers")) implementation(project(":analysis:analysis-api")) + implementation(project(":analysis:analysis-internal-utils")) implementation(project(":analysis:project-structure")) implementation(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) } } diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/IDEKotlinAsJavaFirSupport.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/IDEKotlinAsJavaFirSupport.kt index 154ae0f6914..71523953e12 100644 --- a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/IDEKotlinAsJavaFirSupport.kt +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/IDEKotlinAsJavaFirSupport.kt @@ -15,8 +15,8 @@ import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport import org.jetbrains.kotlin.asJava.classes.KtFakeLightClass import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName +import org.jetbrains.kotlin.light.classes.symbol.caches.SymbolLightClassFacadeCache import org.jetbrains.kotlin.light.classes.symbol.classes.getOrCreateFirLightClass -import org.jetbrains.kotlin.light.classes.symbol.classes.getOrCreateFirLightFacade import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.parentOrNull @@ -90,7 +90,9 @@ class IDEKotlinAsJavaFirSupport(private val project: Project) : KotlinAsJavaSupp override fun getFacadeClasses(facadeFqName: FqName, scope: GlobalSearchScope): Collection = //TODO Split by modules findFilesForFacade(facadeFqName, scope).ifNotEmpty { - listOfNotNull(getOrCreateFirLightFacade(this.toList(), facadeFqName)) + listOfNotNull( + project.getService(SymbolLightClassFacadeCache::class.java).getOrCreateSymbolLightFacade(this.toList(), facadeFqName) + ) } ?: emptyList() override fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection = @@ -105,7 +107,10 @@ class IDEKotlinAsJavaFirSupport(private val project: Project) : KotlinAsJavaSupp .asSequence() .filter { it.isFromSource() } .groupBy { it.javaFileFacadeFqName } - .mapNotNull { getOrCreateFirLightFacade(it.value, it.key) } + .mapNotNull { + project.getService(SymbolLightClassFacadeCache::class.java) + .getOrCreateSymbolLightFacade(it.value, it.key) + } override fun getFacadeNames(packageFqName: FqName, scope: GlobalSearchScope): Collection = project.createDeclarationProvider(scope) diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/caches/SymbolLightClassFacadeCache.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/caches/SymbolLightClassFacadeCache.kt new file mode 100644 index 00000000000..8b405bfb719 --- /dev/null +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/caches/SymbolLightClassFacadeCache.kt @@ -0,0 +1,44 @@ +/* + * 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.light.classes.symbol.caches + +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker +import org.jetbrains.kotlin.light.classes.symbol.FirLightClassForFacade +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.analysis.utils.caches.* +import java.util.concurrent.ConcurrentHashMap + +class SymbolLightClassFacadeCache(project: Project) { + private val cache by softCachedValue( + project, + project.createProjectWideOutOfBlockModificationTracker() + ) { + ConcurrentHashMap() + } + + fun getOrCreateSymbolLightFacade( + ktFiles: List, + facadeClassFqName: FqName, + ): FirLightClassForFacade? { + if (ktFiles.isEmpty()) return null + val key = FacadeKey(facadeClassFqName, ktFiles.toSet()) + return cache.computeIfAbsent(key) { + getOrCreateFirLightFacadeNoCache(ktFiles, facadeClassFqName) + } + } + + private fun getOrCreateFirLightFacadeNoCache( + ktFiles: List, + facadeClassFqName: FqName, + ): FirLightClassForFacade { + val firstFile = ktFiles.first() + return FirLightClassForFacade(firstFile.manager, facadeClassFqName, ktFiles) + } + + private data class FacadeKey(val fqName: FqName, val files: Set) +} \ No newline at end of file diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/firLightClassUtils.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/firLightClassUtils.kt index 161ffa49acf..199721a2df6 100644 --- a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/firLightClassUtils.kt +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/classes/firLightClassUtils.kt @@ -92,28 +92,8 @@ internal fun KtClassOrObjectSymbol.createLightClassNoCache(manager: PsiManager): } } -fun getOrCreateFirLightFacade( - ktFiles: List, - facadeClassFqName: FqName, -): FirLightClassForFacade? { - val firstFile = ktFiles.firstOrNull() ?: return null - //TODO Make caching keyed by all files - return CachedValuesManager.getCachedValue(firstFile) { - CachedValueProvider.Result - .create( - getOrCreateFirLightFacadeNoCache(ktFiles, facadeClassFqName), - firstFile.project.createProjectWideOutOfBlockModificationTracker() - ) - } -} -fun getOrCreateFirLightFacadeNoCache( - ktFiles: List, - facadeClassFqName: FqName, -): FirLightClassForFacade? { - val firstFile = ktFiles.firstOrNull() ?: return null - return FirLightClassForFacade(firstFile.manager, facadeClassFqName, ktFiles) -} + private fun lightClassForEnumEntry(ktEnumEntry: KtEnumEntry): KtLightClass? { diff --git a/prepare/ide-plugin-dependencies/high-level-api-impl-base-for-ide/build.gradle.kts b/prepare/ide-plugin-dependencies/high-level-api-impl-base-for-ide/build.gradle.kts index 215dfcd2907..8523afb0ca4 100644 --- a/prepare/ide-plugin-dependencies/high-level-api-impl-base-for-ide/build.gradle.kts +++ b/prepare/ide-plugin-dependencies/high-level-api-impl-base-for-ide/build.gradle.kts @@ -2,4 +2,10 @@ plugins { kotlin("jvm") } -publishJarsForIde(listOf(":analysis:analysis-api-impl-barebone", ":analysis:analysis-api-impl-base")) +publishJarsForIde( + listOf( + ":analysis:analysis-api-impl-barebone", + ":analysis:analysis-api-impl-base", + ":analysis:analysis-internal-utils" + ) +) diff --git a/settings.gradle b/settings.gradle index 54a57fca36d..ff117c61f64 100644 --- a/settings.gradle +++ b/settings.gradle @@ -482,6 +482,7 @@ include ":generators:analysis-api-generator", ":analysis:analysis-api-impl-barebone", ":analysis:analysis-api-impl-base", ":analysis:analysis-api-providers", + ":analysis:analysis-internal-utils", ":analysis:symbol-light-classes", ":analysis:project-structure", ":analysis:analysis-api-fe10"