Fetch analysis results if it is available

The general idea is to try to fetch a full analysis if it is existed in PerFileAnalysisCache instead of go to ResolveElementCache and do analysis there. Do not perform analysis if it is not in PerFileAnalysisCache.

#KT-37467 Fixed
This commit is contained in:
Vladimir Dolzhenko
2020-03-07 21:55:13 +01:00
parent 7cf8697e30
commit ae4629a5a6
3 changed files with 59 additions and 15 deletions
@@ -57,7 +57,15 @@ internal class ModuleResolutionFacadeImpl(
override fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext {
ResolveInDispatchThreadManager.assertNoResolveInDispatchThread()
if (elements.isEmpty()) return BindingContext.EMPTY
when (elements.size) {
0 -> return BindingContext.EMPTY
1 -> {
runWithCancellationCheck {
projectFacade.fetchAnalysisResultsForElement(elements.first())?.bindingContext
}?.let { return it }
}
}
val resolveElementCache = getFrontendService(elements.first(), ResolveElementCache::class.java)
return runWithCancellationCheck {
resolveElementCache.resolveToElements(elements, bodyResolveMode)
@@ -28,7 +28,9 @@ import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.clearInBlockModifications
import org.jetbrains.kotlin.idea.caches.trackers.inBlockModifications
import org.jetbrains.kotlin.idea.project.*
import org.jetbrains.kotlin.idea.project.IdeaModuleStructureOracle
import org.jetbrains.kotlin.idea.project.findAnalyzerServices
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.*
@@ -40,6 +42,7 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import java.util.*
import java.util.concurrent.locks.ReentrantLock
internal class PerFileAnalysisCache(val file: KtFile, componentProvider: ComponentProvider) {
private val globalContext = componentProvider.get<GlobalContext>()
@@ -50,13 +53,30 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
private val cache = HashMap<PsiElement, AnalysisResult>()
private var fileResult: AnalysisResult? = null
private val lock = ReentrantLock()
fun getAnalysisResults(element: KtElement): AnalysisResult {
internal fun fetchAnalysisResults(element: KtElement): AnalysisResult? {
assert(element.containingKtFile == file) { "Wrong file. Expected $file, but was ${element.containingKtFile}" }
if (lock.tryLock()) {
try {
updateFileResultFromCache()
return fileResult?.takeIf { file.inBlockModifications.isEmpty() }
} finally {
lock.unlock()
}
}
return null
}
internal fun getAnalysisResults(element: KtElement): AnalysisResult {
assert(element.containingKtFile == file) { "Wrong file. Expected $file, but was ${element.containingKtFile}" }
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element)
return synchronized(this) {
lock.lock()
try {
ProgressIndicatorProvider.checkCanceled()
// step 1: perform incremental analysis IF it is applicable
@@ -67,27 +87,21 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
// step 2: return result if it is cached
lookUp(analyzableParent)?.let {
return@synchronized it
return it
}
// step 3: perform analyze of analyzableParent as nothing has been cached yet
val result = analyze(analyzableParent)
cache[analyzableParent] = result
return@synchronized result
return result
} finally {
lock.unlock()
}
}
private fun getIncrementalAnalysisResult(): AnalysisResult? {
// move fileResult from cache if it is stored there
if (fileResult == null && cache.containsKey(file)) {
fileResult = cache[file]
// drop existed results for entire cache:
// if incremental analysis is applicable it will produce a single value for file
// otherwise those results are potentially stale
cache.clear()
}
updateFileResultFromCache()
val inBlockModifications = file.inBlockModifications
if (inBlockModifications.isNotEmpty()) {
@@ -139,6 +153,18 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
return fileResult
}
private fun updateFileResultFromCache() {
// move fileResult from cache if it is stored there
if (fileResult == null && cache.containsKey(file)) {
fileResult = cache[file]
// drop existed results for entire cache:
// if incremental analysis is applicable it will produce a single value for file
// otherwise those results are potentially stale
cache.clear()
}
}
private fun lookUp(analyzableElement: KtElement): AnalysisResult? {
// Looking for parent elements that are already analyzed
// Also removing all elements whose parents are already analyzed, to guarantee consistency
@@ -136,6 +136,16 @@ internal class ProjectResolutionFacade(
return AnalysisResult.success(bindingContext, findModuleDescriptor(elements.first().getModuleInfo()))
}
internal fun fetchAnalysisResultsForElement(element: KtElement): AnalysisResult? {
val slruCache = synchronized(analysisResults) {
analysisResults.upToDateOrNull?.get() ?: return null
}
val perFileCache = synchronized(slruCache) {
slruCache[element.containingKtFile]
}
return perFileCache.fetchAnalysisResults(element)
}
override fun toString(): String {
return "$debugString@${Integer.toHexString(hashCode())}"
}