diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt index b4ab7cfed61..104321cbf0f 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/ComponentsContainers.kt @@ -8,6 +8,8 @@ package org.jetbrains.kotlin.fir.session import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.analysis.CheckersComponent +import org.jetbrains.kotlin.fir.caches.FirCachesFactory +import org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCachesFactory import org.jetbrains.kotlin.fir.extensions.FirExtensionService import org.jetbrains.kotlin.fir.extensions.FirPredicateBasedProvider import org.jetbrains.kotlin.fir.extensions.FirRegisteredPluginAnnotations @@ -40,6 +42,12 @@ fun FirSession.registerCommonComponents(languageVersionSettings: LanguageVersion register(InferenceComponents::class, InferenceComponents(this)) } +@OptIn(SessionConfiguration::class) +fun FirSession.registerThreadUnsafeCaches() { + register(FirCachesFactory::class, FirThreadUnsafeCachesFactory) +} + + // -------------------------- Resolve components -------------------------- /* diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirSessionFactory.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirSessionFactory.kt index 14e399385b7..be690cedefd 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirSessionFactory.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirSessionFactory.kt @@ -19,6 +19,8 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationChecker import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers import org.jetbrains.kotlin.fir.analysis.checkersComponent import org.jetbrains.kotlin.fir.analysis.extensions.additionalCheckers +import org.jetbrains.kotlin.fir.caches.FirCachesFactory +import org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCachesFactory import org.jetbrains.kotlin.fir.checkers.registerCommonCheckers import org.jetbrains.kotlin.fir.extensions.BunchOfRegisteredExtensions import org.jetbrains.kotlin.fir.extensions.extensionService @@ -68,6 +70,7 @@ object FirSessionFactory { init: FirSessionConfigurator.() -> Unit = {} ): FirJavaModuleBasedSession { return FirJavaModuleBasedSession(moduleInfo, sessionProvider).apply { + registerThreadUnsafeCaches() registerCommonComponents(languageVersionSettings) registerResolveComponents() registerJavaSpecificResolveComponents() @@ -113,6 +116,7 @@ object FirSessionFactory { val kotlinClassFinder = VirtualFileFinderFactory.getInstance(project).create(scope) return FirLibrarySession(moduleInfo, sessionProvider).apply { + registerThreadUnsafeCaches() registerCommonComponents(languageVersionSettings) val javaSymbolProvider = JavaSymbolProvider(this, project, scope) diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirCacheWithPostCompute.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirCacheWithPostCompute.kt new file mode 100644 index 00000000000..2921e4f95bf --- /dev/null +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirCacheWithPostCompute.kt @@ -0,0 +1,15 @@ +/* + * 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.fir.caches + +abstract class FirCache { + abstract fun getValue(key: KEY, context: CONTEXT): VALUE + abstract fun getValueIfComputed(key: KEY): VALUE? +} + +@Suppress("NOTHING_TO_INLINE") +inline fun FirCache.getValue(key: KEY): VALUE = + getValue(key, null) diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirCachesFactory.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirCachesFactory.kt new file mode 100644 index 00000000000..4cc8d948c0f --- /dev/null +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirCachesFactory.kt @@ -0,0 +1,74 @@ +/* + * 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.fir.caches + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.FirSessionComponent + +abstract class FirCachesFactory : FirSessionComponent { + /** + * Creates a cache with returns a value by key on demand if it is computed + * Otherwise computes the value in [createValue] and caches it for future invocations + * + * [FirCache.getValue] should not be called inside [createValue] + * + * Where: + * [CONTEXT] -- type of value which be used to create value by [createValue] + */ + abstract fun createCache(createValue: (KEY, CONTEXT) -> VALUE): FirCache + + /** + * Creates a cache with returns a caches value on demand if it is computed + * Otherwise computes the value in two phases: + * - [createValue] -- creates values and stores [VALUE] to cache and passes [VALUE] & [DATA] to [postCompute] + * - [postCompute] -- performs some operations on computed value after it placed into map + * + * [FirCache.getValue] can be safely called in postCompute from the same thread and correct value computed by [createValue] will be returned + * [FirCache.getValue] should not be called inside [createValue] + * + * Where: + * [CONTEXT] -- type of value which be used to create value by [createValue] + * [DATA] -- type of additional data which will be passed from [createValue] to [postCompute] + */ + abstract fun createCacheWithPostCompute( + createValue: (KEY, CONTEXT) -> Pair, + postCompute: (KEY, VALUE, DATA) -> Unit + ): FirCache +} + +val FirSession.firCachesFactory: FirCachesFactory by FirSession.sessionComponentAccessor() + +inline fun FirCachesFactory.createCache( + crossinline createValue: (KEY) -> VALUE, +): FirCache = createCache( + createValue = { key, _ -> createValue(key) }, +) + +inline fun FirCachesFactory.createCacheWithPostCompute( + crossinline createValue: (KEY, CONTEXT) -> VALUE, + crossinline postCompute: (KEY, VALUE) -> Unit +): FirCache = createCacheWithPostCompute( + createValue = { key, context -> createValue(key, context) to null }, + postCompute = { key, value, _ -> postCompute(key, value) } +) + +inline fun FirCachesFactory.createCacheWithPostCompute( + crossinline createValue: (KEY) -> VALUE, + crossinline postCompute: (KEY, VALUE) -> Unit +): FirCache = createCacheWithPostCompute( + createValue = { key, _ -> createValue(key) to null }, + postCompute = { key, value, _ -> postCompute(key, value) } +) + +inline fun FirCachesFactory.createCacheWithPostCompute( + crossinline createValue: (KEY) -> Pair, + crossinline postCompute: (KEY, VALUE, DATA) -> Unit +): FirCache = createCacheWithPostCompute( + createValue = { key, _ -> createValue(key) }, + postCompute = { key, value, data -> postCompute(key, value, data) } +) + + diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirThreadUnsafeCachesFactory.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirThreadUnsafeCachesFactory.kt new file mode 100644 index 00000000000..5532f99dfbc --- /dev/null +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/FirThreadUnsafeCachesFactory.kt @@ -0,0 +1,55 @@ +/* + * 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.fir.caches + +object FirThreadUnsafeCachesFactory : FirCachesFactory() { + override fun createCache(createValue: (KEY, CONTEXT) -> VALUE): FirCache = + FirThreadUnsafeCache(createValue) + + override fun createCacheWithPostCompute( + createValue: (KEY, CONTEXT) -> Pair, + postCompute: (KEY, VALUE, DATA) -> Unit + ): FirCache = + FirThreadUnsafeCacheWithPostCompute(createValue, postCompute) +} + +@Suppress("UNCHECKED_CAST") +private class FirThreadUnsafeCache( + private val createValue: (KEY, CONTEXT) -> VALUE +) : FirCache() { + private val map = NullableMap() + + override fun getValue(key: KEY, context: CONTEXT): VALUE = + map.getOrElse(key) { + createValue(key, context).also { createdValue -> + map[key] = createdValue + } + } + + override fun getValueIfComputed(key: KEY): VALUE? = + map.getOrElse(key) { null as VALUE } +} + + +private class FirThreadUnsafeCacheWithPostCompute( + private val createValue: (KEY, CONTEXT) -> Pair, + private val postCompute: (KEY, VALUE, DATA) -> Unit +) : FirCache() { + private val map = NullableMap() + + override fun getValue(key: KEY, context: CONTEXT): VALUE = + map.getOrElse(key) { + val (createdValue, data) = createValue(key, context) + map[key] = createdValue + postCompute(key, createdValue, data) + createdValue + } + + + @Suppress("UNCHECKED_CAST") + override fun getValueIfComputed(key: KEY): VALUE? = + map.getOrElse(key) { null as VALUE } +} \ No newline at end of file diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/NullableMap.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/NullableMap.kt new file mode 100644 index 00000000000..a94ccf03b4c --- /dev/null +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/caches/NullableMap.kt @@ -0,0 +1,36 @@ +/* + * 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.fir.caches + +import org.jetbrains.kotlin.fir.PrivateForInline + +/** + * [Map] which allows store null values + */ +@OptIn(PrivateForInline::class) +internal inline class NullableMap(private val map: MutableMap = HashMap()) { + + /** + * Get value if it is present in map + * Execute [orElse] otherwise and return it result, + * [orElse] can modify the map inside + */ + @Suppress("UNCHECKED_CAST") + inline fun getOrElse(key: KEY, orElse: () -> VALUE): VALUE = + when (val value = map[key]) { + null -> orElse() + NullValue -> null + else -> value + } as VALUE + + @Suppress("NOTHING_TO_INLINE") + inline operator fun set(key: KEY, value: VALUE) { + map[key] = value ?: NullValue + } +} + +@PrivateForInline +internal object NullValue \ No newline at end of file