diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index e9be7fd57c8..333b2dde83b 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -44330,6 +44330,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/reflection/typeOf/annotatedType.kt"); } + @Test + @TestMetadata("caching.kt") + public void testCaching() throws Exception { + runTest("compiler/testData/codegen/box/reflection/typeOf/caching.kt"); + } + @Test @TestMetadata("classes.kt") public void testClasses() throws Exception { diff --git a/compiler/testData/codegen/box/reflection/typeOf/caching.kt b/compiler/testData/codegen/box/reflection/typeOf/caching.kt new file mode 100644 index 00000000000..255632699d6 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/typeOf/caching.kt @@ -0,0 +1,24 @@ +// TARGET_BACKEND: JVM_IR +// WITH_REFLECT + +import kotlin.test.assertEquals +import kotlin.test.assertSame +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +private inline fun check(isNullable: Boolean = false) { + val t1 = typeOf() + val t2 = typeOf() + assertSame(t1.classifier, t2.classifier) + assertEquals(isNullable, t1.isMarkedNullable) + assertEquals(isNullable, t2.isMarkedNullable) +} + +fun box(): String { + check() + check(true) + check?>(true) + check>() + check?>(true) + return "OK" +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 00faa8f1e9b..2fff5dd6b0d 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -44330,6 +44330,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/reflection/typeOf/annotatedType.kt"); } + @Test + @TestMetadata("caching.kt") + public void testCaching() throws Exception { + runTest("compiler/testData/codegen/box/reflection/typeOf/caching.kt"); + } + @Test @TestMetadata("classes.kt") public void testClasses() throws Exception { diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ClassCaches.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ClassCaches.kt deleted file mode 100644 index 2b2a456095b..00000000000 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ClassCaches.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2010-2022 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 kotlin.reflect.jvm.internal - -import kotlin.reflect.KDeclarationContainer - - -private val K_CLASS_CACHE = createCache { KClassImpl(it) } -private val K_PACKAGE_CACHE = createCache { KPackageImpl(it) } - -// This function is invoked on each reflection access to Java classes, properties, etc. Performance is critical here. -@Suppress("UNCHECKED_CAST") -internal fun getOrCreateKotlinClass(jClass: Class): KClassImpl = K_CLASS_CACHE.get(jClass) as KClassImpl - -internal fun getOrCreateKotlinPackage(jClass: Class): KDeclarationContainer = K_PACKAGE_CACHE.get(jClass) - -internal fun clearCaches() { - K_CLASS_CACHE.clear() - K_PACKAGE_CACHE.clear() -} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionFactoryImpl.java b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionFactoryImpl.java index 1cfa691bc9c..a1430ff196e 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionFactoryImpl.java +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionFactoryImpl.java @@ -33,17 +33,17 @@ public class ReflectionFactoryImpl extends ReflectionFactory { @Override public KDeclarationContainer getOrCreateKotlinPackage(Class javaClass, String moduleName) { // moduleName is unused deliberately and only left in public ABI - return ClassCachesKt.getOrCreateKotlinPackage(javaClass); + return CachesKt.getOrCreateKotlinPackage(javaClass); } @Override public KClass getOrCreateKotlinClass(Class javaClass) { - return ClassCachesKt.getOrCreateKotlinClass(javaClass); + return CachesKt.getOrCreateKotlinClass(javaClass); } @Override public KClass getOrCreateKotlinClass(Class javaClass, String internalName) { - return ClassCachesKt.getOrCreateKotlinClass(javaClass); + return CachesKt.getOrCreateKotlinClass(javaClass); } @Override @@ -111,6 +111,17 @@ public class ReflectionFactoryImpl extends ReflectionFactory { @Override public KType typeOf(KClassifier klass, List arguments, boolean isMarkedNullable) { + /* + * We control how this method is called and ensure that `typeOf` is invoked mostly (in scenarios that + * bother us performance-wise) on the result of `getOrCreateKotlinClass(klass)`, thus we do downcast + * and extract `java.lang.Class` in a zero-cost manner. + * If that's our case, we go to caching code-path that caches relatively slow `createType()` call, + * and, what's more important, all member-based computations on this KType (e.g. `classifier`) + * are properly cached as well. + */ + if (klass instanceof ClassBasedDeclarationContainer) { + return CachesKt.getOrCreateKType(((ClassBasedDeclarationContainer) klass).getJClass(), arguments, isMarkedNullable); + } return KClassifiers.createType(klass, arguments, isMarkedNullable, Collections.emptyList()); } @@ -155,7 +166,7 @@ public class ReflectionFactoryImpl extends ReflectionFactory { // Misc public static void clearCaches() { - ClassCachesKt.clearCaches(); + CachesKt.clearCaches(); ModuleByClassLoaderKt.clearModuleByClassLoaderCache(); } } diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/caches.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/caches.kt new file mode 100644 index 00000000000..0d0703b2157 --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/caches.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2022 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 kotlin.reflect.jvm.internal + +import java.util.concurrent.ConcurrentHashMap +import kotlin.reflect.KType +import kotlin.reflect.KTypeProjection +import kotlin.reflect.full.createType + +import kotlin.reflect.KDeclarationContainer + + +private val K_CLASS_CACHE = createCache { KClassImpl(it) } +private val K_PACKAGE_CACHE = createCache { KPackageImpl(it) } + +// This function is invoked on each reflection access to Java classes, properties, etc. Performance is critical here. +@Suppress("UNCHECKED_CAST") +internal fun getOrCreateKotlinClass(jClass: Class): KClassImpl = K_CLASS_CACHE.get(jClass) as KClassImpl + +internal fun getOrCreateKotlinPackage(jClass: Class): KDeclarationContainer = K_PACKAGE_CACHE.get(jClass) + +internal fun clearCaches() { + K_CLASS_CACHE.clear() + K_PACKAGE_CACHE.clear() + CACHE_FOR_BASE_CLASSIFIERS.clear() + CACHE_FOR_NULLABLE_BASE_CLASSIFIERS.clear() + CACHE_FOR_GENERIC_CLASSIFIERS.clear() +} + +// typeOf-related caches + +// Without type arguments and nullability +private val CACHE_FOR_BASE_CLASSIFIERS = createCache { + getOrCreateKotlinClass(it).createType(emptyList(), false, emptyList()) +} + +private val CACHE_FOR_NULLABLE_BASE_CLASSIFIERS = createCache { + getOrCreateKotlinClass(it).createType(emptyList(), true, emptyList()) +} + +private typealias Key = Pair, Boolean> +// Class -> ((type arguments, is nullable) -> type) +private val CACHE_FOR_GENERIC_CLASSIFIERS = createCache> { + ConcurrentHashMap() +} + +internal fun getOrCreateKType(jClass: Class, arguments: List, isMarkedNullable: Boolean): KType { + return if (arguments.isEmpty()) { + if (isMarkedNullable) { + CACHE_FOR_NULLABLE_BASE_CLASSIFIERS.get(jClass) + } else { + CACHE_FOR_BASE_CLASSIFIERS.get(jClass) + } + } else { + getOrCreateKTypeWithTypeArguments(jClass, arguments, isMarkedNullable) + } +} + +private fun getOrCreateKTypeWithTypeArguments( + jClass: Class, + arguments: List, + isMarkedNullable: Boolean +): KType { + val cache = CACHE_FOR_GENERIC_CLASSIFIERS.get(jClass) + return cache.getOrPut(arguments to isMarkedNullable) { + getOrCreateKotlinClass(jClass).createType(arguments, isMarkedNullable, emptyList()) + } +} + +