From ec3da62672877cbb6f4623537efbfc57004cd7cb Mon Sep 17 00:00:00 2001 From: Hung Nguyen Date: Thu, 13 Oct 2022 19:32:14 +0100 Subject: [PATCH] Extract KotlinClassInfo to a separate class to reduce the size of IncrementalJvmCache and prepare for the next change. ^KT-54144 In progress Handle changes to inline functions/property accessors with `@JvmName`s If we detect a change in an inline function `foo` with @JvmName `fooJvmName`, we have two options: 1. Report that function `foo` has changed 2. Report that method `fooJvmName` has changed Similarly, if we detect a change in an inline property accessor with JvmName `getFoo` of property `foo`, we have two options: 1. Report that property `foo` has changed 2. Report that property accessor `getFoo` has changed The compiler is guaranteed to generate `LookupSymbol`s corresponding to option 1 when referencing inline functions/property accessors, but it is not guaranteed to generate `LookupSymbol`s corresponding to option 2. (Currently the compiler seems to support option 2 for *inline* functions/property accessors, but that may change.) Therefore, we will choose option 1 as it is cleaner and safer. ^KT-54144 In progress Ignore inline functions that are not found in the bytecode ^KT-54144 In progress Add unit test for handling `@JvmName`s Test: KotlinOnlyClasspathChangesComputerTest #testFunctionsAndPropertyAccessorsWithJvmNames ^KT-54144 Fixed Small cleanup in IncrementalCompilerRunner - Add comment for closing caches - Rename providedChangedFiles to changedFiles - Tiny clean up in `performWorkBeforeCompilation` - Count directories to delete in debug logs ^KT-53015 In progress Small cleanup in IncrementalCompilerRunner - Add comment for closing caches - Rename providedChangedFiles to changedFiles - Tiny clean up in `performWorkBeforeCompilation` - Count directories to delete in debug logs ^KT-53015 In progress --- .../kotlin/incremental/ChangesCollector.kt | 8 +- .../kotlin/incremental/IncrementalJvmCache.kt | 243 +++--------------- .../kotlin/incremental/KotlinClassInfo.kt | 206 +++++++++++++++ .../incremental/storage/externalizers.kt | 138 ++++++---- .../org/jetbrains/kotlin/inline/inlineUtil.kt | 90 ++++--- .../incremental/AbiSnapshotDiffService.kt | 4 +- .../incremental/IncrementalCompilerRunner.kt | 42 +-- .../IncrementalJvmCompilerRunner.kt | 6 +- .../ClasspathSnapshotSerializer.kt | 51 +--- .../classpathDiff/ClasspathSnapshotter.kt | 5 +- .../ClasspathChangesComputerTest.kt | 26 +- .../com/example/SomeClass.class | Bin 0 -> 841 bytes .../com/example/SomeClassKt.class | Bin 0 -> 812 bytes .../com/example/SomeClass.class | Bin 0 -> 841 bytes .../com/example/SomeClassKt.class | Bin 0 -> 812 bytes .../com/example/SomeClass.kt | 20 ++ .../com/example/SomeClass.kt | 20 ++ .../kotlin/codegen/InlineTestUtil.kt | 4 +- .../kotlin/benchmarks/GenerateIrRuntime.kt | 3 +- 19 files changed, 501 insertions(+), 365 deletions(-) create mode 100644 build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClass.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClassKt.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClass.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClassKt.class create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/current-classpath/com/example/SomeClass.kt create mode 100644 compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src/previous-classpath/com/example/SomeClass.kt diff --git a/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt b/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt index d28e0ce336b..9603244c671 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/ChangesCollector.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.incremental +import org.jetbrains.kotlin.incremental.DifferenceCalculatorForClass.Companion.getNonPrivateMembers +import org.jetbrains.kotlin.incremental.DifferenceCalculatorForPackageFacade.Companion.getNonPrivateMembers import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.deserialization.NameResolver import org.jetbrains.kotlin.name.FqName @@ -166,7 +168,7 @@ class ChangesCollector { } private fun PackagePartProtoData.collectAllFromPackage(isRemoved: Boolean) { - val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true) + val memberNames = getNonPrivateMembers() if (isRemoved) { collectRemovedMembers(packageFqName, memberNames) } else { @@ -178,14 +180,14 @@ class ChangesCollector { val classFqName = nameResolver.getClassId(proto.fqName).asSingleFqName() if (proto.isCompanionObject) { - val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true) + val memberNames = getNonPrivateMembers() val collectMember = if (isRemoved) this@ChangesCollector::collectRemovedMember else this@ChangesCollector::collectChangedMember collectMember(classFqName.parent(), classFqName.shortName().asString()) memberNames.forEach { collectMember(classFqName, it) } } else { if (!isRemoved && collectAllMembersForNewClass) { - val memberNames = getNonPrivateMemberNames(includeInlineAccessors = true) + val memberNames = getNonPrivateMembers() memberNames.forEach { this@ChangesCollector.collectChangedMember(classFqName, it) } } diff --git a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt index 41248f93b7e..7341a97bc60 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalJvmCache.kt @@ -23,27 +23,20 @@ import com.intellij.util.io.EnumeratorStringDescriptor import gnu.trove.THashSet import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.build.GeneratedJvmClass -import org.jetbrains.kotlin.incremental.DifferenceCalculatorForClass.Companion.getNonPrivateMembers -import org.jetbrains.kotlin.incremental.DifferenceCalculatorForPackageFacade.Companion.getNonPrivateMembers import org.jetbrains.kotlin.incremental.storage.* -import org.jetbrains.kotlin.inline.inlineAccessors -import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors +import org.jetbrains.kotlin.inline.InlineFunction +import org.jetbrains.kotlin.inline.InlineFunctionOrAccessor +import org.jetbrains.kotlin.inline.InlinePropertyAccessor import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.JvmPackagePartProto import org.jetbrains.kotlin.metadata.ProtoBuf -import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding -import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.jvm.JvmClassName -import org.jetbrains.org.objectweb.asm.* -import org.jetbrains.org.objectweb.asm.ClassReader.SKIP_CODE -import org.jetbrains.org.objectweb.asm.ClassReader.SKIP_DEBUG import java.io.File import java.security.MessageDigest @@ -439,7 +432,7 @@ open class IncrementalJvmCache( // todo: reuse code with InlineFunctionsMap? private inner class ConstantsMap(storageFile: File) : - BasicStringMap>(storageFile, LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer)) { + BasicStringMap>(storageFile, MapExternalizer(StringExternalizer, ConstantValueExternalizer)) { operator fun contains(className: JvmClassName): Boolean = className.internalName in storage @@ -487,7 +480,7 @@ open class IncrementalJvmCache( storage.remove(className.internalName) } - override fun dumpValue(value: LinkedHashMap): String = + override fun dumpValue(value: Map): String = value.dumpMap(Any::toString) } @@ -567,7 +560,10 @@ open class IncrementalJvmCache( } private inner class InlineFunctionsMap(storageFile: File) : - BasicStringMap>(storageFile, LinkedHashMapExternalizer(StringExternalizer, LongExternalizer)) { + BasicStringMap>( + storageFile, + MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer) + ) { @Synchronized fun process(kotlinClassInfo: KotlinClassInfo, changesCollector: ChangesCollector) { @@ -581,34 +577,36 @@ open class IncrementalJvmCache( storage.remove(key) } - for (methodSignature in oldMap.keys + newMap.keys) { - val inlineFunctionOrAccessorName = functionNameBySignature(methodSignature) - // Note: If we detect a change in an inline property accessor (e.g., `getFoo`), we have two options: - // 1. Report that property `foo` has changed (and ignore `getFoo`) - // 2. Report that property accessor `getFoo` has changed (and ignore `foo`) - // Both options are possible (the compiler generates `LookupSymbol`s for both `foo` and `getFoo` when `getFoo` is inlined). - // Currently, we choose option 2 as it is simpler (i.e., even though inline property accessors are not immediate members of - // a class/package, we treat them like immediate members). - changesCollector.collectMemberIfValueWasChanged( - kotlinClassInfo.scopeFqName(), - inlineFunctionOrAccessorName, - oldMap[methodSignature], - newMap[methodSignature] - ) + // Note: If we detect a change in an inline function `foo` with @JvmName `fooJvmName`, we have two options: + // 1. Report that function `foo` has changed + // 2. Report that method `fooJvmName` has changed + // + // Similarly, if we detect a change in an inline property accessor with JvmName `getFoo` of property `foo`, we have two options: + // 1. Report that property `foo` has changed + // 2. Report that property accessor `getFoo` has changed + // + // The compiler is guaranteed to generate `LookupSymbol`s corresponding to option 1 when referencing inline functions/property + // accessors, but it is not guaranteed to generate `LookupSymbol`s corresponding to option 2. (Currently the compiler seems to + // support option 2 for *inline* functions/property accessors, but that may change.) + // + // In the following, we will choose option 1 as it is cleaner and safer. + val scope = kotlinClassInfo.scopeFqName() + (oldMap.keys + newMap.keys).forEach { + val name = when (it) { + is InlineFunction -> it.kotlinFunctionName + is InlinePropertyAccessor -> it.propertyName + } + changesCollector.collectMemberIfValueWasChanged(scope, name, oldMap[it], newMap[it]) } } - // TODO get name in better way instead of using substringBefore - private fun functionNameBySignature(signature: String): String = - signature.substringBefore("(") - @Synchronized fun remove(className: JvmClassName) { storage.remove(className.internalName) } - override fun dumpValue(value: LinkedHashMap): String = - value.dumpMap { java.lang.Long.toHexString(it) } + override fun dumpValue(value: Map): String = + value.mapKeys { it.key.jvmMethodSignature.asString() }.dumpMap { java.lang.Long.toHexString(it) } } private fun KotlinClassInfo.scopeFqName() = when (classKind) { @@ -669,182 +667,3 @@ fun , V> Map.dumpMap(dumpValue: (V) -> String): String = @TestOnly fun > Collection.dumpCollection(): String = "[${sorted().joinToString(", ", transform = Any::toString)}]" - -/** - * @param includeInlineAccessors If `true`, also include inline property accessors in the returned list. This is necessary because property - * accessors are not immediate members of a class/package, and in some cases we treat them like immediate members (see - * [org.jetbrains.kotlin.incremental.IncrementalJvmCache.InlineFunctionsMap.process]). Note that here we deal with *inline* property - * accessors only as [org.jetbrains.kotlin.incremental.IncrementalJvmCache.InlineFunctionsMap.process] is written only to handle *inline* - * property accessors (and functions). - */ -fun ProtoData.getNonPrivateMemberNames(includeInlineAccessors: Boolean): List { - return when (this) { - is ClassProtoData -> { - getNonPrivateMembers() + (if (includeInlineAccessors) { - inlineAccessors(proto.propertyList, nameResolver, excludePrivateAccessors = true).map { it.name } - } else emptyList()) - } - is PackagePartProtoData -> { - getNonPrivateMembers() + (if (includeInlineAccessors) { - inlineAccessors(proto.propertyList, nameResolver, excludePrivateAccessors = true).map { it.name } - } else emptyList()) - } - } -} - -/** - * Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile` - * task (see [IncrementalJvmCache.saveClassToCache]). - * - * It's important that this class contain only the minimal required information, as it will be part of the classpath snapshot of the - * `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures, - * and should not contain private method signatures, or method implementations. - */ -class KotlinClassInfo constructor( - val classId: ClassId, - val classKind: KotlinClassHeader.Kind, - val classHeaderData: Array, // Can be empty - val classHeaderStrings: Array, // Can be empty - val multifileClassName: String?, // Not null iff classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART - val constantsMap: LinkedHashMap, - val inlineFunctionsAndAccessorsMap: LinkedHashMap -) { - - val className: JvmClassName by lazy { JvmClassName.byClassId(classId) } - - val protoMapValue: ProtoMapValue by lazy { - ProtoMapValue( - isPackageFacade = classKind != KotlinClassHeader.Kind.CLASS, - BitEncoding.decodeBytes(classHeaderData), - classHeaderStrings - ) - } - - /** - * The [ProtoData] of this class. - * - * NOTE: The caller needs to ensure `classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS` first, as the compiler doesn't write proto - * data to [KotlinClassHeader.Kind.MULTIFILE_CLASS] classes. - */ - val protoData: ProtoData by lazy { - check(classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS) { - "Proto data is not available for KotlinClassHeader.Kind.MULTIFILE_CLASS: $classId" - } - protoMapValue.toProtoData(classId.packageFqName) - } - - /** Name of the companion object of this class (default is "Companion") iff this class HAS a companion object, or null otherwise. */ - val companionObject: ClassId? by lazy { - if (classKind == KotlinClassHeader.Kind.CLASS) { - (protoData as ClassProtoData).getCompanionObjectName()?.let { - classId.createNestedClassId(Name.identifier(it)) - } - } else null - } - - /** List of constants defined in this class iff this class IS a companion object, or null otherwise. The list could be empty. */ - val constantsInCompanionObject: List? by lazy { - if (classKind == KotlinClassHeader.Kind.CLASS) { - val classProtoData = protoData as ClassProtoData - if (classProtoData.proto.isCompanionObject) { - classProtoData.getConstants() - } else null - } else null - } - - companion object { - - fun createFrom(kotlinClass: LocalFileKotlinClass): KotlinClassInfo { - return createFrom(kotlinClass.classId, kotlinClass.classHeader, kotlinClass.fileContents) - } - - fun createFrom(classId: ClassId, classHeader: KotlinClassHeader, classContents: ByteArray): KotlinClassInfo { - val (constants, inlineFunctionsAndAccessors) = getConstantsAndInlineFunctionsOrAccessors(classHeader, classContents) - - return KotlinClassInfo( - classId, - classHeader.kind, - classHeader.data ?: emptyArray(), - classHeader.strings ?: emptyArray(), - classHeader.multifileClassName, - constants.mapKeysTo(LinkedHashMap()) { it.key.name }, - inlineFunctionsAndAccessors.mapKeysTo(LinkedHashMap()) { it.key.asString() }, - ) - } - } -} - -/** - * Parses the class file only once to get both constants and inline functions/property accessors. This is faster than getting them - * separately in two passes. - */ -private fun getConstantsAndInlineFunctionsOrAccessors( - classHeader: KotlinClassHeader, - classContents: ByteArray -): Pair, Map> { - val constantsClassVisitor = ConstantsClassVisitor() - val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(classHeader) - - return if (inlineFunctionsAndAccessors.isEmpty()) { - // parsingOptions = (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info are not important for constants - ClassReader(classContents).accept(constantsClassVisitor, SKIP_CODE or SKIP_DEBUG) - Pair(constantsClassVisitor.getResult(), emptyMap()) - } else { - val inlineFunctionsAndAccessorsClassVisitor = - InlineFunctionsAndAccessorsClassVisitor(inlineFunctionsAndAccessors.toSet(), constantsClassVisitor) - // parsingOptions must not include (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info (e.g., line numbers) are important for - // inline functions/accessors - ClassReader(classContents).accept(inlineFunctionsAndAccessorsClassVisitor, 0) - Pair(constantsClassVisitor.getResult(), inlineFunctionsAndAccessorsClassVisitor.getResult()) - } -} - -private class ConstantsClassVisitor : ClassVisitor(Opcodes.API_VERSION) { - private val result = mutableMapOf() - - override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? { - if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null - - val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL - if (value != null && access and staticFinal == staticFinal) { - result[JvmMemberSignature.Field(name, desc)] = value - } - return null - } - - fun getResult() = result -} - -private class InlineFunctionsAndAccessorsClassVisitor( - private val inlineFunctionsAndAccessors: Set, - cv: ConstantsClassVisitor // Note: cv must not override `visitMethod` (it will not be called with the current implementation below) -) : ClassVisitor(Opcodes.API_VERSION, cv) { - - private val result = mutableMapOf() - private var classVersion: Int? = null - - override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array?) { - super.visit(version, access, name, signature, superName, interfaces) - classVersion = version - } - - override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { - if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null - - val method = JvmMemberSignature.Method(name, desc) - if (method !in inlineFunctionsAndAccessors) return null - - val classWriter = ClassWriter(0) - - // The `version` and `name` parameters are important (see KT-38857), the others can be null. - classWriter.visit(/* version */ classVersion!!, /* access */ 0, /* name */ "ClassWithOneMethod", null, null, null) - - return object : MethodVisitor(Opcodes.API_VERSION, classWriter.visitMethod(access, name, desc, signature, exceptions)) { - override fun visitEnd() { - result[method] = classWriter.toByteArray().md5() - } - } - } - - fun getResult() = result -} diff --git a/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt b/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt new file mode 100644 index 00000000000..57b121791bf --- /dev/null +++ b/build-common/src/org/jetbrains/kotlin/incremental/KotlinClassInfo.kt @@ -0,0 +1,206 @@ +/* + * 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 org.jetbrains.kotlin.incremental + +import com.intellij.util.io.DataExternalizer +import org.jetbrains.kotlin.incremental.storage.* +import org.jetbrains.kotlin.inline.InlineFunctionOrAccessor +import org.jetbrains.kotlin.inline.inlineFunctionsAndAccessors +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader +import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.org.objectweb.asm.* + +/** + * Minimal information about a Kotlin class to compute recompilation-triggering changes during an incremental run of the `KotlinCompile` + * task (see [IncrementalJvmCache.saveClassToCache]). + * + * It's important that this class contain only the minimal required information, as it will be part of the classpath snapshot of the + * `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures, + * and should not contain private method signatures, or method implementations. + */ +class KotlinClassInfo constructor( + val classId: ClassId, + val classKind: KotlinClassHeader.Kind, + val classHeaderData: Array, // Can be empty + val classHeaderStrings: Array, // Can be empty + val multifileClassName: String?, // Not null iff classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART + val constantsMap: Map, + val inlineFunctionsAndAccessorsMap: Map +) { + + val className: JvmClassName by lazy { JvmClassName.byClassId(classId) } + + val protoMapValue: ProtoMapValue by lazy { + ProtoMapValue( + isPackageFacade = classKind != KotlinClassHeader.Kind.CLASS, + BitEncoding.decodeBytes(classHeaderData), + classHeaderStrings + ) + } + + /** + * The [ProtoData] of this class. + * + * NOTE: The caller needs to ensure `classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS` first, as the compiler doesn't write proto + * data to [KotlinClassHeader.Kind.MULTIFILE_CLASS] classes. + */ + val protoData: ProtoData by lazy { + check(classKind != KotlinClassHeader.Kind.MULTIFILE_CLASS) { + "Proto data is not available for KotlinClassHeader.Kind.MULTIFILE_CLASS: $classId" + } + protoMapValue.toProtoData(classId.packageFqName) + } + + /** Name of the companion object of this class (default is "Companion") iff this class HAS a companion object, or null otherwise. */ + val companionObject: ClassId? by lazy { + if (classKind == KotlinClassHeader.Kind.CLASS) { + (protoData as ClassProtoData).getCompanionObjectName()?.let { + classId.createNestedClassId(Name.identifier(it)) + } + } else null + } + + /** List of constants defined in this class iff this class IS a companion object, or null otherwise. The list could be empty. */ + val constantsInCompanionObject: List? by lazy { + if (classKind == KotlinClassHeader.Kind.CLASS) { + val classProtoData = protoData as ClassProtoData + if (classProtoData.proto.isCompanionObject) { + classProtoData.getConstants() + } else null + } else null + } + + companion object { + + fun createFrom(kotlinClass: LocalFileKotlinClass): KotlinClassInfo { + return createFrom(kotlinClass.classId, kotlinClass.classHeader, kotlinClass.fileContents) + } + + fun createFrom(classId: ClassId, classHeader: KotlinClassHeader, classContents: ByteArray): KotlinClassInfo { + val (constants, inlineFunctionsAndAccessors) = getConstantsAndInlineFunctionsOrAccessors(classHeader, classContents) + + return KotlinClassInfo( + classId, + classHeader.kind, + classHeader.data ?: emptyArray(), + classHeader.strings ?: emptyArray(), + classHeader.multifileClassName, + constants.mapKeys { it.key.name }, + inlineFunctionsAndAccessors + ) + } + } +} + +/** + * Parses the class file only once to get both constants and inline functions/property accessors. This is faster than getting them + * separately in two passes. + */ +private fun getConstantsAndInlineFunctionsOrAccessors( + classHeader: KotlinClassHeader, + classContents: ByteArray +): Pair, Map> { + val constantsClassVisitor = ConstantsClassVisitor() + val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(classHeader, excludePrivateMembers = true) + + return if (inlineFunctionsAndAccessors.isEmpty()) { + // Handle this case differently to improve performance + // parsingOptions = (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info are not important for constants + ClassReader(classContents).accept(constantsClassVisitor, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG) + Pair(constantsClassVisitor.getResult(), emptyMap()) + } else { + val inlineFunctionsAndAccessorsClassVisitor = InlineFunctionsAndAccessorsClassVisitor( + inlineFunctionsAndAccessors.map { it.jvmMethodSignature }.toSet(), + constantsClassVisitor + ) + // parsingOptions must not include (SKIP_CODE, SKIP_DEBUG) as method bodies and debug info (e.g., line numbers) are important for + // inline functions/accessors + ClassReader(classContents).accept(inlineFunctionsAndAccessorsClassVisitor, 0) + val constantsMap = constantsClassVisitor.getResult() + val methodHashesMap = inlineFunctionsAndAccessorsClassVisitor.getResult() + val inlineFunctionsAndAccessorsMap = inlineFunctionsAndAccessors.mapNotNull { inline -> + // Note that internal/private inline functions may be removed from the bytecode if code shrinker is used. For example, + // `kotlin-reflect-1.7.20.jar` contains `/kotlin/reflect/jvm/internal/UtilKt.class` in which the internal inline function + // `reflectionCall` appears in the Kotlin metadata (also in the source file), but not in the bytecode. + // When that happens (i.e., when the map lookup below returns null), we will ignore the method. It is safe to ignore because the + // method is not declared in the bytecode and therefore can't be referenced. + methodHashesMap[inline.jvmMethodSignature]?.let { inline to it } + }.toMap() + Pair(constantsMap, inlineFunctionsAndAccessorsMap) + } +} + +private class ConstantsClassVisitor : ClassVisitor(Opcodes.API_VERSION) { + private val result = mutableMapOf() + + override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? { + if (access and Opcodes.ACC_PRIVATE == Opcodes.ACC_PRIVATE) return null + + val staticFinal = Opcodes.ACC_STATIC or Opcodes.ACC_FINAL + if (value != null && access and staticFinal == staticFinal) { + result[JvmMemberSignature.Field(name, desc)] = value + } + return null + } + + fun getResult() = result +} + +private class InlineFunctionsAndAccessorsClassVisitor( + private val inlineFunctionsAndAccessors: Set, + cv: ConstantsClassVisitor // Note: cv must not override `visitMethod` (it will not be called with the current implementation below) +) : ClassVisitor(Opcodes.API_VERSION, cv) { + + private val result = mutableMapOf() + private var classVersion: Int? = null + + override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array?) { + super.visit(version, access, name, signature, superName, interfaces) + classVersion = version + } + + override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { + // Note: Do not filter out private methods here because a *public* inline function may actually have a *private* corresponding JVM + // method in the bytecode (see `InlineOnlyKt.isInlineOnlyPrivateInBytecode`). + // Just filter the methods based on the given `inlineFunctionsAndAccessors` set. + val method = JvmMemberSignature.Method(name, desc) + if (method !in inlineFunctionsAndAccessors) return null + + val classWriter = ClassWriter(0) + + // The `version` and `name` parameters are important (see KT-38857), the others can be null. + classWriter.visit(/* version */ classVersion!!, /* access */ 0, /* name */ "ClassWithOneMethod", null, null, null) + + return object : MethodVisitor(Opcodes.API_VERSION, classWriter.visitMethod(access, name, desc, signature, exceptions)) { + override fun visitEnd() { + result[method] = classWriter.toByteArray().md5() + } + } + } + + fun getResult() = result +} + +/** + * [DataExternalizer] for the value of a Kotlin constant. + * + * The constants' values are provided by ASM (see the javadoc of [ConstantsClassVisitor.visitField]), so their types can only be the + * following: Integer, Long, Float, Double, String. (Boolean constants have Integer (0, 1) values in ASM.) + */ +object ConstantValueExternalizer : DataExternalizer by DelegateDataExternalizer( + listOf( + java.lang.Integer::class.java, + java.lang.Long::class.java, + java.lang.Float::class.java, + java.lang.Double::class.java, + java.lang.String::class.java + ), + listOf(IntExternalizer, LongExternalizer, FloatExternalizer, DoubleExternalizer, StringExternalizer) +) diff --git a/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt b/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt index a5c2fd2d45f..a5d46ff8e87 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/storage/externalizers.kt @@ -22,6 +22,10 @@ import com.intellij.util.io.DataExternalizer import com.intellij.util.io.EnumeratorStringDescriptor import com.intellij.util.io.IOUtil import com.intellij.util.io.KeyDescriptor +import org.jetbrains.kotlin.inline.InlineFunction +import org.jetbrains.kotlin.inline.InlineFunctionOrAccessor +import org.jetbrains.kotlin.inline.InlinePropertyAccessor +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.jvm.JvmClassName @@ -165,53 +169,6 @@ object StringToLongMapExternalizer : StringMapExternalizer() { } } -/** [DataExternalizer] for a Kotlin constant. */ -object ConstantExternalizer : DataExternalizer { - - override fun save(output: DataOutput, value: Any) { - when (value) { - is Int -> { - output.writeByte(Kind.INT.ordinal) - output.writeInt(value) - } - is Float -> { - output.writeByte(Kind.FLOAT.ordinal) - output.writeFloat(value) - } - is Long -> { - output.writeByte(Kind.LONG.ordinal) - output.writeLong(value) - } - is Double -> { - output.writeByte(Kind.DOUBLE.ordinal) - output.writeDouble(value) - } - is String -> { - output.writeByte(Kind.STRING.ordinal) - output.writeString(value) - } - else -> throw IllegalStateException("Unexpected constant class: ${value::class.java}") - } - } - - override fun read(input: DataInput): Any { - return when (Kind.values()[input.readByte().toInt()]) { - Kind.INT -> input.readInt() - Kind.FLOAT -> input.readFloat() - Kind.LONG -> input.readLong() - Kind.DOUBLE -> input.readDouble() - Kind.STRING -> input.readString() - } - } - - // The constants' values are provided by ASM, so their types can only be the following. - // See https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html#visitField(int,java.lang.String,java.lang.String,java.lang.String,java.lang.Object) - // (Note: Boolean constants have Integer (0, 1) values in ASM.) - private enum class Kind { - INT, FLOAT, LONG, DOUBLE, STRING - } -} - fun DataExternalizer.saveToFile(file: File, value: T) { return DataOutputStream(FileOutputStream(file).buffered()).use { save(it, value) @@ -248,6 +205,16 @@ object LongExternalizer : DataExternalizer { override fun read(input: DataInput): Long = input.readLong() } +object FloatExternalizer : DataExternalizer { + override fun save(output: DataOutput, value: Float) = output.writeFloat(value) + override fun read(input: DataInput): Float = input.readFloat() +} + +object DoubleExternalizer : DataExternalizer { + override fun save(output: DataOutput, value: Double) = output.writeDouble(value) + override fun read(input: DataInput): Double = input.readDouble() +} + object StringExternalizer : DataExternalizer { override fun save(output: DataOutput, value: String) = IOUtil.writeString(value, output) override fun read(input: DataInput): String = IOUtil.readString(input) @@ -268,6 +235,33 @@ object PathStringDescriptor : EnumeratorStringDescriptor() { } } +/** [DataExternalizer] that delegates to another [DataExternalizer] depending on the type of the object to externalize. */ +class DelegateDataExternalizer( + val types: List>, + val typesExternalizers: List> +) : DataExternalizer { + + init { + check(types.size == typesExternalizers.size) + check(types.size < Byte.MAX_VALUE) // We will writeByte(index), so we need lastIndex (types.size - 1) <= Byte.MAX_VALUE + } + + override fun save(output: DataOutput, objectToExternalize: T) { + val type = types.single { it.isAssignableFrom(objectToExternalize!!::class.java) } + val typeIndex = types.indexOf(type) + + output.writeByte(typeIndex) + @Suppress("UNCHECKED_CAST") + (typesExternalizers[typeIndex] as DataExternalizer).save(output, objectToExternalize) + } + + override fun read(input: DataInput): T { + val typeIndex = input.readByte().toInt() + @Suppress("UNCHECKED_CAST") + return typesExternalizers[typeIndex].read(input) as T + } +} + /** * [DataExternalizer] for a [Collection]. * @@ -401,3 +395,53 @@ class LinkedHashMapExternalizer( keyExternalizer: DataExternalizer, valueExternalizer: DataExternalizer ) : MapExternalizer>(keyExternalizer, valueExternalizer, { size -> LinkedHashMap(size) }) + +object JvmMethodSignatureExternalizer : DataExternalizer { + + override fun save(output: DataOutput, method: JvmMemberSignature.Method) { + StringExternalizer.save(output, method.name) + StringExternalizer.save(output, method.desc) + } + + override fun read(input: DataInput): JvmMemberSignature.Method { + return JvmMemberSignature.Method( + name = StringExternalizer.read(input), + desc = StringExternalizer.read(input) + ) + } +} + +object InlineFunctionOrAccessorExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf(InlineFunction::class.java, InlinePropertyAccessor::class.java), + typesExternalizers = listOf(InlineFunctionExternalizer, InlinePropertyAccessorExternalizer) +) + +private object InlineFunctionExternalizer : DataExternalizer { + + override fun save(output: DataOutput, function: InlineFunction) { + JvmMethodSignatureExternalizer.save(output, function.jvmMethodSignature) + StringExternalizer.save(output, function.kotlinFunctionName) + } + + override fun read(input: DataInput): InlineFunction { + return InlineFunction( + jvmMethodSignature = JvmMethodSignatureExternalizer.read(input), + kotlinFunctionName = StringExternalizer.read(input) + ) + } +} + +private object InlinePropertyAccessorExternalizer : DataExternalizer { + + override fun save(output: DataOutput, accessor: InlinePropertyAccessor) { + JvmMethodSignatureExternalizer.save(output, accessor.jvmMethodSignature) + StringExternalizer.save(output, accessor.propertyName) + } + + override fun read(input: DataInput): InlinePropertyAccessor { + return InlinePropertyAccessor( + jvmMethodSignature = JvmMethodSignatureExternalizer.read(input), + propertyName = StringExternalizer.read(input) + ) + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt index edd4e01c5cc..c33c8d4d341 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/inline/inlineUtil.kt @@ -24,27 +24,44 @@ import org.jetbrains.kotlin.metadata.deserialization.NameResolver import org.jetbrains.kotlin.metadata.deserialization.TypeTable import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf -import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.JvmMethodSignature import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.serialization.deserialization.ProtoEnumFlags import org.jetbrains.kotlin.serialization.deserialization.descriptorVisibility -fun inlineFunctionsAndAccessors(header: KotlinClassHeader): List { +sealed interface InlineFunctionOrAccessor { + val jvmMethodSignature: JvmMemberSignature.Method +} + +data class InlineFunction( + override val jvmMethodSignature: JvmMemberSignature.Method, + + /** The Kotlin name of the function. It may be different from the JVM name of the function if [JvmName] is used. */ + val kotlinFunctionName: String +) : InlineFunctionOrAccessor + +data class InlinePropertyAccessor( + override val jvmMethodSignature: JvmMemberSignature.Method, + + /** The name of the property that this property accessor belongs to. */ + val propertyName: String +) : InlineFunctionOrAccessor + +fun inlineFunctionsAndAccessors(header: KotlinClassHeader, excludePrivateMembers: Boolean = false): List { val data = header.data ?: return emptyList() val strings = header.strings ?: return emptyList() return when (header.kind) { KotlinClassHeader.Kind.CLASS -> { val (nameResolver, classProto) = JvmProtoBufUtil.readClassDataFrom(data, strings) - inlineFunctions(classProto.functionList, nameResolver, classProto.typeTable) + - inlineAccessors(classProto.propertyList, nameResolver) + inlineFunctions(classProto.functionList, nameResolver, classProto.typeTable, excludePrivateMembers) + + inlinePropertyAccessors(classProto.propertyList, nameResolver, excludePrivateMembers) } KotlinClassHeader.Kind.FILE_FACADE, KotlinClassHeader.Kind.MULTIFILE_CLASS_PART -> { val (nameResolver, packageProto) = JvmProtoBufUtil.readPackageDataFrom(data, strings) - inlineFunctions(packageProto.functionList, nameResolver, packageProto.typeTable) + - inlineAccessors(packageProto.propertyList, nameResolver) + inlineFunctions(packageProto.functionList, nameResolver, packageProto.typeTable, excludePrivateMembers) + + inlinePropertyAccessors(packageProto.propertyList, nameResolver, excludePrivateMembers) } else -> emptyList() } @@ -53,40 +70,51 @@ fun inlineFunctionsAndAccessors(header: KotlinClassHeader): List, nameResolver: NameResolver, - protoTypeTable: ProtoBuf.TypeTable -): List { + protoTypeTable: ProtoBuf.TypeTable, + excludePrivateFunctions: Boolean = false +): List { val typeTable = TypeTable(protoTypeTable) - return functions.filter { Flags.IS_INLINE.get(it.flags) }.mapNotNull { - JvmProtoBufUtil.getJvmMethodSignature(it, nameResolver, typeTable) - } + return functions + .filter { Flags.IS_INLINE.get(it.flags) && (!excludePrivateFunctions || !isPrivate(it.flags)) } + .mapNotNull { inlineFunction -> + JvmProtoBufUtil.getJvmMethodSignature(inlineFunction, nameResolver, typeTable)?.let { + InlineFunction(jvmMethodSignature = it, kotlinFunctionName = nameResolver.getString(inlineFunction.name)) + } + } } -fun inlineAccessors( +private fun inlinePropertyAccessors( properties: List, nameResolver: NameResolver, excludePrivateAccessors: Boolean = false -): List { - val inlineAccessors = mutableListOf() - - fun isInline(flags: Int) = Flags.IS_INLINE_ACCESSOR.get(flags) - fun isPrivate(flags: Int) = DescriptorVisibilities.isPrivate(ProtoEnumFlags.descriptorVisibility(Flags.VISIBILITY.get(flags))) - +): List { + val inlineAccessors = mutableListOf() properties.forEach { property -> val propertySignature = property.getExtensionOrNull(JvmProtoBuf.propertySignature) ?: return@forEach - - if (property.hasGetterFlags() && isInline(property.getterFlags)) { - if (!(excludePrivateAccessors && isPrivate(property.getterFlags))) { - inlineAccessors.add(propertySignature.getter) - } + if (property.hasGetterFlags() && Flags.IS_INLINE_ACCESSOR.get(property.getterFlags) + && (!excludePrivateAccessors || !isPrivate(property.getterFlags)) + ) { + val getter = propertySignature.getter + inlineAccessors.add( + InlinePropertyAccessor( + JvmMemberSignature.Method(name = nameResolver.getString(getter.name), desc = nameResolver.getString(getter.desc)), + propertyName = nameResolver.getString(property.name) + ) + ) } - if (property.hasSetterFlags() && isInline(property.setterFlags)) { - if (!(excludePrivateAccessors && isPrivate(property.setterFlags))) { - inlineAccessors.add(propertySignature.setter) - } + if (property.hasSetterFlags() && Flags.IS_INLINE_ACCESSOR.get(property.setterFlags) + && (!excludePrivateAccessors || !isPrivate(property.setterFlags)) + ) { + val setter = propertySignature.setter + inlineAccessors.add( + InlinePropertyAccessor( + JvmMemberSignature.Method(name = nameResolver.getString(setter.name), desc = nameResolver.getString(setter.desc)), + propertyName = nameResolver.getString(property.name) + ) + ) } } - - return inlineAccessors.map { - JvmMemberSignature.Method(name = nameResolver.getString(it.name), desc = nameResolver.getString(it.desc)) - } + return inlineAccessors } + +private fun isPrivate(flags: Int) = DescriptorVisibilities.isPrivate(ProtoEnumFlags.descriptorVisibility(Flags.VISIBILITY.get(flags))) diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt index 59ed70b99bc..c5a7452d8a9 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/AbiSnapshotDiffService.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.incremental +import org.jetbrains.kotlin.incremental.DifferenceCalculatorForClass.Companion.getNonPrivateMembers import org.jetbrains.kotlin.metadata.ProtoBuf.Visibility.PRIVATE import org.jetbrains.kotlin.metadata.deserialization.Flags import org.jetbrains.kotlin.name.FqName @@ -97,8 +98,7 @@ class AbiSnapshotDiffService() { when (protoData) { is ClassProtoData -> { fqNames.add(fqName) - symbols.addAll( - protoData.getNonPrivateMemberNames(includeInlineAccessors = true).map { LookupSymbol(it, fqName.asString()) }) + symbols.addAll(protoData.getNonPrivateMembers().map { LookupSymbol(it, fqName.asString()) }) } is PackagePartProtoData -> { symbols.addAll( diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt index 3f9593bee7f..b9cc910bee0 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalCompilerRunner.kt @@ -18,12 +18,15 @@ package org.jetbrains.kotlin.incremental import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS import org.jetbrains.kotlin.build.GeneratedFile -import org.jetbrains.kotlin.build.report.* +import org.jetbrains.kotlin.build.report.BuildReporter +import org.jetbrains.kotlin.build.report.debug +import org.jetbrains.kotlin.build.report.info import org.jetbrains.kotlin.build.report.metrics.BuildAttribute import org.jetbrains.kotlin.build.report.metrics.BuildAttribute.* import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric import org.jetbrains.kotlin.build.report.metrics.BuildTime import org.jetbrains.kotlin.build.report.metrics.measure +import org.jetbrains.kotlin.build.report.warn import org.jetbrains.kotlin.cli.common.* import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.messages.MessageCollector @@ -80,12 +83,12 @@ abstract class IncrementalCompilerRunner< allSourceFiles: List, args: Args, messageCollector: MessageCollector, - // when [providedChangedFiles] is not null, changes are provided by external system (e.g. Gradle) + // when `changedFiles` is not null, changes are provided by external system (e.g. Gradle) // otherwise we track source files changes ourselves. - providedChangedFiles: ChangedFiles?, + changedFiles: ChangedFiles?, projectDir: File? = null ): ExitCode = reporter.measure(BuildTime.INCREMENTAL_COMPILATION_DAEMON) { - return when (val result = tryCompileIncrementally(allSourceFiles, providedChangedFiles, args, projectDir, messageCollector)) { + return when (val result = tryCompileIncrementally(allSourceFiles, changedFiles, args, projectDir, messageCollector)) { is ICResult.Completed -> { reporter.debug { "Incremental compilation completed" } result.exitCode @@ -95,7 +98,7 @@ abstract class IncrementalCompilerRunner< reporter.addAttribute(result.reason) compileNonIncrementally( - result.reason, allSourceFiles, args, projectDir, trackChangedFiles = providedChangedFiles == null, messageCollector + result.reason, allSourceFiles, args, projectDir, trackChangedFiles = changedFiles == null, messageCollector ) } is ICResult.Failed -> { @@ -114,7 +117,7 @@ abstract class IncrementalCompilerRunner< reporter.addAttribute(result.reason) compileNonIncrementally( - result.reason, allSourceFiles, args, projectDir, trackChangedFiles = providedChangedFiles == null, messageCollector + result.reason, allSourceFiles, args, projectDir, trackChangedFiles = changedFiles == null, messageCollector ) } } @@ -141,23 +144,24 @@ abstract class IncrementalCompilerRunner< */ private fun tryCompileIncrementally( allSourceFiles: List, - providedChangedFiles: ChangedFiles?, + changedFiles: ChangedFiles?, args: Args, projectDir: File?, messageCollector: MessageCollector ): ICResult { - if (providedChangedFiles is ChangedFiles.Unknown) { + if (changedFiles is ChangedFiles.Unknown) { return ICResult.RequiresRebuild(UNKNOWN_CHANGES_IN_GRADLE_INPUTS) } - providedChangedFiles as ChangedFiles.Known? + changedFiles as ChangedFiles.Known? val caches = createCacheManager(args, projectDir) val exitCode: ExitCode try { // Step 1: Get changed files - val changedFiles: ChangedFiles.Known = try { - getChangedFiles(providedChangedFiles, allSourceFiles, caches) + val knownChangedFiles: ChangedFiles.Known = try { + getChangedFiles(changedFiles, allSourceFiles, caches) } catch (e: Throwable) { + // Don't need to close caches in cases where we return `ICResult.Failed` because we will compile non-incrementally anyway return ICResult.Failed(IC_FAILED_TO_GET_CHANGED_FILES, e) } @@ -166,7 +170,7 @@ abstract class IncrementalCompilerRunner< // Step 2: Compute files to recompile val compilationMode = try { reporter.measure(BuildTime.IC_CALCULATE_INITIAL_DIRTY_SET) { - calculateSourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot ?: emptyMap()) + calculateSourcesToCompile(caches, knownChangedFiles, args, messageCollector, classpathAbiSnapshot ?: emptyMap()) } } catch (e: Throwable) { return ICResult.Failed(IC_FAILED_TO_COMPUTE_FILES_TO_RECOMPILE, e) @@ -227,7 +231,7 @@ abstract class IncrementalCompilerRunner< check(it.containsAll(mainOutputDirs)) { "outputDirs is missing classesDir and workingDir: $it" } } ?: mainOutputDirs - reporter.debug { "Cleaning output directories" } + reporter.debug { "Cleaning ${outputDirsToClean.size} output directories" } cleanOrCreateDirectories(outputDirsToClean) } return createCacheManager(args, projectDir).use { caches -> @@ -266,20 +270,20 @@ abstract class IncrementalCompilerRunner< } private fun getChangedFiles( - providedChangedFiles: ChangedFiles.Known?, + changedFiles: ChangedFiles.Known?, allSourceFiles: List, caches: CacheManager ): ChangedFiles.Known { return when { - providedChangedFiles == null -> caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles) - providedChangedFiles.forDependencies -> { + changedFiles == null -> caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles) + changedFiles.forDependencies -> { val moreChangedFiles = caches.inputsCache.sourceSnapshotMap.compareAndUpdate(allSourceFiles) ChangedFiles.Known( - modified = providedChangedFiles.modified + moreChangedFiles.modified, - removed = providedChangedFiles.removed + moreChangedFiles.removed + modified = changedFiles.modified + moreChangedFiles.modified, + removed = changedFiles.removed + moreChangedFiles.removed ) } - else -> providedChangedFiles + else -> changedFiles } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt index 2f27915a9c4..822396f93d2 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/IncrementalJvmCompilerRunner.kt @@ -109,7 +109,7 @@ fun makeIncrementally( classpathChanges = ClasspathSnapshotDisabled ) //TODO set properly - compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null) + compiler.compile(sourceFiles, args, messageCollector, changedFiles = null) } } @@ -388,9 +388,7 @@ open class IncrementalJvmCompilerRunner( super.performWorkBeforeCompilation(compilationMode, args) if (compilationMode is CompilationMode.Incremental) { - val destinationDir = args.destinationAsFile - destinationDir.mkdirs() - args.classpathAsList = listOf(destinationDir) + args.classpathAsList + args.classpathAsList = listOf(args.destinationAsFile) + args.classpathAsList } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt index f41bfbb8e21..d7d399cbfa1 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotSerializer.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.incremental.classpathDiff import com.intellij.util.containers.Interner import com.intellij.util.io.DataExternalizer import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric +import org.jetbrains.kotlin.incremental.ConstantValueExternalizer import org.jetbrains.kotlin.incremental.KotlinClassInfo import org.jetbrains.kotlin.incremental.storage.* import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader @@ -53,27 +54,6 @@ object CachedClasspathSnapshotSerializer { } } -internal open class DataExternalizerForSealedClass( - val baseClass: Class, - val inheritorClasses: List>, - val inheritorExternalizers: List> -) : DataExternalizer { - - override fun save(output: DataOutput, objectToExternalize: T) { - val inheritorClassIndex = - inheritorClasses.indexOfFirst { it.isAssignableFrom(objectToExternalize!!::class.java) }.also { check(it != -1) } - output.writeByte(inheritorClassIndex.also { check(it <= Byte.MAX_VALUE) }) // Write byte so the data is smaller - @Suppress("UNCHECKED_CAST") - (inheritorExternalizers[inheritorClassIndex] as DataExternalizer).save(output, objectToExternalize) - } - - override fun read(input: DataInput): T { - val inheritorClassIndex = input.readByte().toInt() - @Suppress("UNCHECKED_CAST") - return inheritorExternalizers[inheritorClassIndex].read(input) as T - } -} - object ClasspathEntrySnapshotExternalizer : DataExternalizer { override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) { @@ -87,26 +67,23 @@ object ClasspathEntrySnapshotExternalizer : DataExternalizer( - baseClass = ClassSnapshot::class.java, - inheritorClasses = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java), - inheritorExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer) +internal object ClassSnapshotExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java), + typesExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer) ) -internal object AccessibleClassSnapshotExternalizer : DataExternalizerForSealedClass( - baseClass = AccessibleClassSnapshot::class.java, - inheritorClasses = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java), - inheritorExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer) +internal object AccessibleClassSnapshotExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java), + typesExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer) ) -private object KotlinClassSnapshotExternalizer : DataExternalizerForSealedClass( - baseClass = KotlinClassSnapshot::class.java, - inheritorClasses = listOf( +private object KotlinClassSnapshotExternalizer : DataExternalizer by DelegateDataExternalizer( + types = listOf( RegularKotlinClassSnapshot::class.java, PackageFacadeKotlinClassSnapshot::class.java, MultifileClassKotlinClassSnapshot::class.java ), - inheritorExternalizers = listOf( + typesExternalizers = listOf( RegularKotlinClassSnapshotExternalizer, PackageFacadeKotlinClassSnapshotExternalizer, MultifileClassKotlinClassSnapshotExternalizer @@ -185,8 +162,8 @@ internal object KotlinClassInfoExternalizer : DataExternalizer ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList()) ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList()) NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName) - LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).save(output, info.constantsMap) - LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap) + MapExternalizer(StringExternalizer, ConstantValueExternalizer).save(output, info.constantsMap) + MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).save(output, info.inlineFunctionsAndAccessorsMap) } override fun read(input: DataInput): KotlinClassInfo { @@ -197,8 +174,8 @@ internal object KotlinClassInfoExternalizer : DataExternalizer classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(), classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(), multifileClassName = NullableValueExternalizer(StringExternalizer).read(input), - constantsMap = LinkedHashMapExternalizer(StringExternalizer, ConstantExternalizer).read(input), - inlineFunctionsAndAccessorsMap = LinkedHashMapExternalizer(StringExternalizer, LongExternalizer).read(input) + constantsMap = MapExternalizer(StringExternalizer, ConstantValueExternalizer).read(input), + inlineFunctionsAndAccessorsMap = MapExternalizer(InlineFunctionOrAccessorExternalizer, LongExternalizer).read(input) ) } } diff --git a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt index 90e1a2d5f28..443ece74095 100644 --- a/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt +++ b/compiler/incremental-compilation-impl/src/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotter.kt @@ -9,9 +9,10 @@ import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter import org.jetbrains.kotlin.build.report.metrics.BuildTime import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter import org.jetbrains.kotlin.build.report.metrics.measure +import org.jetbrains.kotlin.incremental.DifferenceCalculatorForPackageFacade.Companion.getNonPrivateMembers import org.jetbrains.kotlin.incremental.KotlinClassInfo +import org.jetbrains.kotlin.incremental.PackagePartProtoData import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL -import org.jetbrains.kotlin.incremental.getNonPrivateMemberNames import org.jetbrains.kotlin.incremental.md5 import org.jetbrains.kotlin.incremental.storage.toByteArray import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.* @@ -96,7 +97,7 @@ object ClassSnapshotter { ) FILE_FACADE, MULTIFILE_CLASS_PART -> PackageFacadeKotlinClassSnapshot( classId, classAbiHash, classMemberLevelSnapshot, - packageMemberNames = kotlinClassInfo.protoData.getNonPrivateMemberNames(includeInlineAccessors = true).toSet() + packageMemberNames = (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMembers().toSet() ) MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot( classId, classAbiHash, classMemberLevelSnapshot, diff --git a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt index 61cf45b6358..72d5c064130 100644 --- a/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt +++ b/compiler/incremental-compilation-impl/test/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest.kt @@ -223,11 +223,9 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { LookupSymbol(name = "inlineProperty_ChangedType", scope = "com.example"), LookupSymbol(name = "inlineProperty_ChangedType_BackingField", scope = "com.example"), - LookupSymbol(name = "getInlineProperty_ChangedType", scope = "com.example"), - LookupSymbol(name = "setInlineProperty_ChangedType", scope = "com.example"), - LookupSymbol(name = "getInlineProperty_ChangedGetterImpl", scope = "com.example"), - LookupSymbol(name = "setInlineProperty_ChangedSetterImpl", scope = "com.example"), + LookupSymbol(name = "inlineProperty_ChangedGetterImpl", scope = "com.example"), + LookupSymbol(name = "inlineProperty_ChangedSetterImpl", scope = "com.example"), LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass") ), @@ -238,6 +236,26 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() { ).assertEquals(changes) } + @Test + fun testFunctionsAndPropertyAccessorsWithJvmNames() { + val changes = computeClasspathChanges(File(testDataDir, "KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/src"), tmpDir) + Changes( + lookupSymbols = setOf( + LookupSymbol(name = "changedFunction", scope = "com.example.SomeClass"), + LookupSymbol(name = "changedPropertyAccessor", scope = "com.example.SomeClass"), + + LookupSymbol(name = "changedInlineFunction", scope = "com.example"), + LookupSymbol(name = "changedInlinePropertyAccessor", scope = "com.example"), + + LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SomeClass"), + ), + fqNames = setOf( + "com.example", + "com.example.SomeClass" + ) + ).assertEquals(changes) + } + /** Tests [SupertypesInheritorsImpact]. */ @Test override fun testImpactComputation_SupertypesInheritors() { diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClass.class new file mode 100644 index 0000000000000000000000000000000000000000..89cf8b70a4e49b86e2c4458ec0938419187fad2b GIT binary patch literal 841 zcma)3O>fgc5PiFL630!6Q_?0)p?ph#5Kb!&NFbzADUcFc)S_H++Bhq^i9af@6O~(j z3wI8jkT{@-BR>i;8$wm_A+Y3`H?#BJ&dz-O_USW#2e`$s8YGb~PepVRO8+Q{_^hPB|B zwB(>Qj)P1k@!llb7m=JR?J-n4qa+Je><=fAzc?e79nWhv7vxuIaw600?M@I#t&{Xj zr1F#gQf6WxGC^D(O)PpSGsZCn3~WUCQ)Qwip}`izi`lg5@;Z09H=DZLF`!7^uFt01 zyvd&CzP!tGkMAveg$8fh&kH7YoWqJ|nfT!|it`QI$$5Drxy{T)wZC-CQ2u2Q45hQl z-ydc4=G|mKb18RJEceGzU#71`Kcvh`CkaH@6{#}uBDrxmjx!a>c05s9QGO?mlT4Tq z(>~mzWSj=Fr3`<4QQG~j5L@81NAp?KJgt@N6^eW_GxE8+AK`o;z;TtVIi@78ku5E7 z5iUg)EK!Ef$@j>cI$nHl>^Y`$j>++Zd7WyOsD7FL3z}dB<^PZ>{adO`ci)(Iu!)=G yTXcg*-B)_B+E{7hVjESg62w{?HPm}x8tZ8EP}FcWHZ<}Ym*$|XQPQ|f4Sokxm!a(d literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClassKt.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/current-classpath/com/example/SomeClassKt.class new file mode 100644 index 0000000000000000000000000000000000000000..aa7e950a5f2e4e708f23e0bbd1a10ad63ab0d6c8 GIT binary patch literal 812 zcma)4&rj4q6#iyf)^2x&vM%B-pn`jl3k74M3GqNkh{Y9+5H5R~ZAa~H+aaB9gEwyc zAKv{3JQ#vE|0v^|%|f!G#>2cH-~0N#*EjRy=htrlp5hV1VyIGIo{ID&k^Z4dIepBwR-1)1)Uduv6 zq7XC{PfLpy!i;f@0RtPFfK%}_HqjkDazG;8S2;9voS1amAfjUb^W>6u&LfHm+njN;aUHvky-X~ijjKjkNitFQ;qY9U@hjeO$&?|$Z zNvK8X|A;lG#Oi+%t5MHY(&t#l8u=I0<2JRJ?ZFB#7r+ZJk4AtyXa-oo-5!|6BJTB2 P(P(M3HEP<#OElRp)6cfR literal 0 HcmV?d00001 diff --git a/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClass.class b/compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest/KotlinOnly/testFunctionsAndPropertyAccessorsWithJvmNames/classes/previous-classpath/com/example/SomeClass.class new file mode 100644 index 0000000000000000000000000000000000000000..8e631dc4575f4691f59602fd6fd79e411bc7bbc9 GIT binary patch literal 841 zcma)3O>fgc5PiFL9M?^WQ_?1Fp?ph#5Kb!&NFbzADc}-X)S_H++Bhrf#2=N{iOMa% zg)0s*s1~#JnX{cF?&|r(<#cW!4c#}KaolPBXYhcrAHfPgq z-eONnU*1{V<$FtRd7Zb+=Vi@p`>^I3nm>F-+*&v7l51(pZKe-u{H0@t>Mw&}sGLpy z{wSwA@1_HqOSKawa(^86W%gS1Bg!my(m+IAk%gKU$&JHtl83SMlS!yT%I_penhQN* zdJpF)9cO`Thg!c@ly-kB#1=Tcqy7wPo}QKB6=I&A8Lg$eA7OtWz;TtL-lin3QLGer z0S>Vm7AeE$;(LXd8}Di7ywX0WDvxYdsh~mVI3GQO62Ftomr6*#lEp!+H-33XZ~tf~9b24ypR6Fg*CjPfjyCn7seWpI#ZayJ!P zzbbElG57;fi69l~L-3|QkP)F4!*cYI5E+LmO_balsi;hH^|H$LMJ5@nN9!R&vpdX7 zst1EA3nsN~23w7LRy#@Oedpgij98{y-WK_>EXq$iQ6#m_i>s0A*XjP+JAb*@Ygvj| zl!CV6S!IzR%ox`gFtDKsI2BJ46KzryZ!tVQKlMC1+;=_h+U;52Zu3p{;^NG``0Q|Z zX073$pBm`d_cKnA)3#milWshI%&;=`)4y-Y%;bfsmP>sZ>Q~pZF)T^S-8?3_=el$W z`=hKci$l>*32AimNTf%iNK8Hfm)?z3nPf6lRiYEZca+LYVQx+{cnA5Yh~!>kzHd!h zkFGUh3mmr5hkOe&aLBJvwqXP#Z(G~emvQwTS%)wVANMJ)k7JIT@F;sgr$z|9GPs(E zT9p0|S#yf4{wJ~;&0HmZj%BQoe@-)Q(TLd|tPpb{{1EeKgt(1nhy~o~foUw_ZV!$| OOQWq((() for (file in files) { val binaryClass = loadBinaryClass(file) - val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader) + val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader).map { it.jvmMethodSignature }.toSet() val classVisitor = object : ClassVisitorWithName() { override fun visitMethod( @@ -81,7 +81,7 @@ object InlineTestUtil { var doLambdaInliningCheck = true for (file in files) { val binaryClass = loadBinaryClass(file) - val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader) + val inlineFunctionsAndAccessors = inlineFunctionsAndAccessors(binaryClass.classHeader).map { it.jvmMethodSignature }.toSet() //if inline function creates anonymous object then do not try to check that all lambdas are inlined val classVisitor = object : ClassVisitorWithName() { diff --git a/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt b/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt index f034cb08d98..e0270f1a8bf 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt @@ -67,7 +67,6 @@ import org.junit.Test import java.io.File import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.createTempDirectory -import kotlin.io.path.createTempFile import org.jetbrains.kotlin.konan.file.File as KonanFile @OptIn(ExperimentalPathApi::class) @@ -325,7 +324,7 @@ class GenerateIrRuntime { buildHistoryFile = buildHistoryFile, modulesApiHistory = EmptyModulesApiHistory ) - compiler.compile(allFiles, args, MessageCollector.NONE, providedChangedFiles = null) + compiler.compile(allFiles, args, MessageCollector.NONE, changedFiles = null) } val cleanBuildTime = System.nanoTime() - cleanBuildStart