Minor: extract most of JPS (old IC) specific part from IncrementalCacheImpl

Original commit: 5889ba4293
This commit is contained in:
Zalim Bashorov
2016-01-18 18:49:33 +03:00
parent 91b1f50150
commit e28f1888e8
3 changed files with 250 additions and 194 deletions
@@ -695,7 +695,7 @@ private fun processChanges(
compiledFiles: Set<File>, compiledFiles: Set<File>,
allCompiledFiles: MutableSet<File>, allCompiledFiles: MutableSet<File>,
dataManager: BuildDataManager, dataManager: BuildDataManager,
caches: Collection<IncrementalCacheImpl>, caches: Collection<JpsIncrementalCacheImpl>,
compilationResult: CompilationResult, compilationResult: CompilationResult,
fsOperations: FSOperationsHelper fsOperations: FSOperationsHelper
) { ) {
@@ -710,7 +710,7 @@ private fun processChanges(
private fun CompilationResult.doProcessChanges( private fun CompilationResult.doProcessChanges(
compiledFiles: Set<File>, compiledFiles: Set<File>,
allCompiledFiles: MutableSet<File>, allCompiledFiles: MutableSet<File>,
caches: Collection<IncrementalCacheImpl>, caches: Collection<JpsIncrementalCacheImpl>,
fsOperations: FSOperationsHelper fsOperations: FSOperationsHelper
) { ) {
KotlinBuilder.LOG.debug("compilationResult = $this") KotlinBuilder.LOG.debug("compilationResult = $this")
@@ -826,7 +826,7 @@ private fun getLookupTracker(project: JpsProject): LookupTracker {
return lookupTracker return lookupTracker
} }
private fun getIncrementalCaches(chunk: ModuleChunk, context: CompileContext): Map<ModuleBuildTarget, IncrementalCacheImpl> { private fun getIncrementalCaches(chunk: ModuleChunk, context: CompileContext): Map<ModuleBuildTarget, JpsIncrementalCacheImpl> {
val dependentTargets = getDependentTargets(chunk, context) val dependentTargets = getDependentTargets(chunk, context)
val dataManager = context.projectDescriptor.dataManager val dataManager = context.projectDescriptor.dataManager
@@ -17,20 +17,15 @@
package org.jetbrains.kotlin.jps.incremental package org.jetbrains.kotlin.jps.incremental
import com.google.protobuf.MessageLite import com.google.protobuf.MessageLite
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName
import com.intellij.util.SmartList import com.intellij.util.SmartList
import com.intellij.util.io.BooleanDataDescriptor import com.intellij.util.io.BooleanDataDescriptor
import com.intellij.util.io.EnumeratorStringDescriptor import com.intellij.util.io.EnumeratorStringDescriptor
import gnu.trove.THashSet
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.jps.builders.storage.BuildDataPaths import org.jetbrains.jps.builders.storage.BuildDataPaths
import org.jetbrains.jps.builders.storage.StorageProvider
import org.jetbrains.jps.incremental.ModuleBuildTarget import org.jetbrains.jps.incremental.ModuleBuildTarget
import org.jetbrains.jps.incremental.storage.BuildDataManager
import org.jetbrains.jps.incremental.storage.PathStringDescriptor import org.jetbrains.jps.incremental.storage.PathStringDescriptor
import org.jetbrains.kotlin.config.IncrementalCompilation import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.jps.build.GeneratedJvmClass import org.jetbrains.kotlin.jps.build.GeneratedJvmClass
import org.jetbrains.kotlin.jps.build.KotlinBuilder import org.jetbrains.kotlin.jps.build.KotlinBuilder
import org.jetbrains.kotlin.jps.incremental.storage.* import org.jetbrains.kotlin.jps.incremental.storage.*
@@ -49,30 +44,30 @@ import org.jetbrains.kotlin.serialization.deserialization.TypeTable
import org.jetbrains.kotlin.serialization.deserialization.supertypes import org.jetbrains.kotlin.serialization.deserialization.supertypes
import org.jetbrains.kotlin.serialization.jvm.BitEncoding import org.jetbrains.kotlin.serialization.jvm.BitEncoding
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.FieldVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import java.io.File import java.io.File
import java.security.MessageDigest import java.security.MessageDigest
import java.util.* import java.util.*
val KOTLIN_CACHE_DIRECTORY_NAME = "kotlin" val KOTLIN_CACHE_DIRECTORY_NAME = "kotlin"
class IncrementalCacheImpl( open class IncrementalCacheImpl(
private val target: ModuleBuildTarget, private val target: ModuleBuildTarget,
paths: BuildDataPaths paths: BuildDataPaths
) : BasicMapsOwner(), IncrementalCache { ) : BasicMapsOwner(), IncrementalCache {
companion object { companion object {
val PROTO_MAP = "proto" private val PROTO_MAP = "proto"
val CONSTANTS_MAP = "constants" private val CONSTANTS_MAP = "constants"
val INLINE_FUNCTIONS = "inline-functions" private val PACKAGE_PARTS = "package-parts"
val PACKAGE_PARTS = "package-parts" private val MULTIFILE_CLASS_FACADES = "multifile-class-facades"
val MULTIFILE_CLASS_FACADES = "multifile-class-facades" private val MULTIFILE_CLASS_PARTS = "multifile-class-parts"
val MULTIFILE_CLASS_PARTS = "multifile-class-parts" private val SOURCE_TO_CLASSES = "source-to-classes"
val SOURCE_TO_CLASSES = "source-to-classes" private val DIRTY_OUTPUT_CLASSES = "dirty-output-classes"
val DIRTY_OUTPUT_CLASSES = "dirty-output-classes" private val SUBTYPES = "subtypes"
val DIRTY_INLINE_FUNCTIONS = "dirty-inline-functions" private val SUPERTYPES = "supertypes"
val INLINED_TO = "inlined-to"
val SUBTYPES = "subtypes"
val SUPERTYPES = "supertypes"
private val MODULE_MAPPING_FILE_NAME = "." + ModuleMapping.MAPPING_FILE_EXT private val MODULE_MAPPING_FILE_NAME = "." + ModuleMapping.MAPPING_FILE_EXT
} }
@@ -86,35 +81,29 @@ class IncrementalCacheImpl(
return registerMap(map) return registerMap(map)
} }
private val String.storageFile: File protected val String.storageFile: File
get() = File(baseDir, this + "." + CACHE_EXTENSION) get() = File(baseDir, this + "." + CACHE_EXTENSION)
private val protoMap = registerMap(ProtoMap(PROTO_MAP.storageFile)) private val protoMap = registerMap(ProtoMap(PROTO_MAP.storageFile))
private val constantsMap = registerMap(ConstantsMap(CONSTANTS_MAP.storageFile)) private val constantsMap = registerMap(ConstantsMap(CONSTANTS_MAP.storageFile))
private val inlineFunctionsMap = registerMap(InlineFunctionsMap(INLINE_FUNCTIONS.storageFile))
private val packagePartMap = registerMap(PackagePartMap(PACKAGE_PARTS.storageFile)) private val packagePartMap = registerMap(PackagePartMap(PACKAGE_PARTS.storageFile))
private val multifileClassFacadeMap = registerMap(MultifileClassFacadeMap(MULTIFILE_CLASS_FACADES.storageFile)) private val multifileClassFacadeMap = registerMap(MultifileClassFacadeMap(MULTIFILE_CLASS_FACADES.storageFile))
private val multifileClassPartMap = registerMap(MultifileClassPartMap(MULTIFILE_CLASS_PARTS.storageFile)) private val multifileClassPartMap = registerMap(MultifileClassPartMap(MULTIFILE_CLASS_PARTS.storageFile))
private val sourceToClassesMap = registerMap(SourceToClassesMap(SOURCE_TO_CLASSES.storageFile)) private val sourceToClassesMap = registerMap(SourceToClassesMap(SOURCE_TO_CLASSES.storageFile))
private val dirtyOutputClassesMap = registerMap(DirtyOutputClassesMap(DIRTY_OUTPUT_CLASSES.storageFile)) private val dirtyOutputClassesMap = registerMap(DirtyOutputClassesMap(DIRTY_OUTPUT_CLASSES.storageFile))
private val dirtyInlineFunctionsMap = registerMap(DirtyInlineFunctionsMap(DIRTY_INLINE_FUNCTIONS.storageFile))
private val inlinedTo = registerMap(InlineFunctionsFilesMap(INLINED_TO.storageFile))
private val subtypesMap = registerExperimentalMap(SubtypesMap(SUBTYPES.storageFile)) private val subtypesMap = registerExperimentalMap(SubtypesMap(SUBTYPES.storageFile))
private val supertypesMap = registerExperimentalMap(SupertypesMap(SUPERTYPES.storageFile)) private val supertypesMap = registerExperimentalMap(SupertypesMap(SUPERTYPES.storageFile))
private val dependents = arrayListOf<IncrementalCacheImpl>() private val dependents = arrayListOf<IncrementalCacheImpl>()
private val outputDir by lazy(LazyThreadSafetyMode.NONE) { requireNotNull(target.outputDir) { "Target is expected to have output directory: $target" } } private val outputDir by lazy(LazyThreadSafetyMode.NONE) { requireNotNull(target.outputDir) { "Target is expected to have output directory: $target" } }
private val dependentsWithThis: Sequence<IncrementalCacheImpl> protected val dependentsWithThis: Sequence<IncrementalCacheImpl>
get() = sequenceOf(this).plus(dependents.asSequence()) get() = sequenceOf(this).plus(dependents.asSequence())
internal val dependentCaches: Iterable<IncrementalCacheImpl> internal val dependentCaches: Iterable<IncrementalCacheImpl>
get() = dependents get() = dependents
override fun registerInline(fromPath: String, jvmSignature: String, toPath: String) { override fun registerInline(fromPath: String, jvmSignature: String, toPath: String) {
if (!IncrementalCompilation.isExperimental()) {
inlinedTo.add(fromPath, jvmSignature, toPath)
}
} }
fun addDependentCache(cache: IncrementalCacheImpl) { fun addDependentCache(cache: IncrementalCacheImpl) {
@@ -132,28 +121,9 @@ class IncrementalCacheImpl(
} }
} }
fun getFilesToReinline(): Collection<File> {
val result = THashSet(FileUtil.PATH_HASHING_STRATEGY)
for ((className, functions) in dirtyInlineFunctionsMap.getEntries()) {
val classFilePath = getClassFilePath(className.internalName)
for (cache in dependentsWithThis) {
val targetFiles = functions.flatMap { cache.inlinedTo[classFilePath, it] }
result.addAll(targetFiles)
}
}
return result.map { File(it) }
}
fun getSubtypesOf(className: FqName): Sequence<FqName> = fun getSubtypesOf(className: FqName): Sequence<FqName> =
subtypesMap[className].asSequence() subtypesMap[className].asSequence()
fun cleanDirtyInlineFunctions() {
dirtyInlineFunctionsMap.clean()
}
override fun getClassFilePath(internalClassName: String): String { override fun getClassFilePath(internalClassName: String): String {
return toSystemIndependentName(File(outputDir, "$internalClassName.class").canonicalPath) return toSystemIndependentName(File(outputDir, "$internalClassName.class").canonicalPath)
} }
@@ -184,7 +154,7 @@ class IncrementalCacheImpl(
protoMap.process(kotlinClass, isPackage = true) + protoMap.process(kotlinClass, isPackage = true) +
constantsMap.process(kotlinClass) + constantsMap.process(kotlinClass) +
inlineFunctionsMap.process(kotlinClass, isPackage = true) additionalProcessChangedClass(kotlinClass, isPackage = true)
} }
header.isCompatibleMultifileClassKind() -> { header.isCompatibleMultifileClassKind() -> {
val partNames = kotlinClass.classHeader.filePartClassNames?.toList() val partNames = kotlinClass.classHeader.filePartClassNames?.toList()
@@ -193,7 +163,7 @@ class IncrementalCacheImpl(
// TODO NO_CHANGES? (delegates only) // TODO NO_CHANGES? (delegates only)
constantsMap.process(kotlinClass) + constantsMap.process(kotlinClass) +
inlineFunctionsMap.process(kotlinClass, isPackage = true) additionalProcessChangedClass(kotlinClass, isPackage = true)
} }
header.isCompatibleMultifileClassPartKind() -> { header.isCompatibleMultifileClassPartKind() -> {
assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" } assert(sourceFiles.size == 1) { "Multifile class part from several source files: $sourceFiles" }
@@ -202,14 +172,14 @@ class IncrementalCacheImpl(
protoMap.process(kotlinClass, isPackage = true) + protoMap.process(kotlinClass, isPackage = true) +
constantsMap.process(kotlinClass) + constantsMap.process(kotlinClass) +
inlineFunctionsMap.process(kotlinClass, isPackage = true) additionalProcessChangedClass(kotlinClass, isPackage = true)
} }
header.isCompatibleClassKind() && !header.isLocalClass -> { header.isCompatibleClassKind() && !header.isLocalClass -> {
addToClassStorage(kotlinClass) addToClassStorage(kotlinClass)
protoMap.process(kotlinClass, isPackage = false) + protoMap.process(kotlinClass, isPackage = false) +
constantsMap.process(kotlinClass) + constantsMap.process(kotlinClass) +
inlineFunctionsMap.process(kotlinClass, isPackage = false) additionalProcessChangedClass(kotlinClass, isPackage = false)
} }
else -> CompilationResult.NO_CHANGES else -> CompilationResult.NO_CHANGES
} }
@@ -218,6 +188,8 @@ class IncrementalCacheImpl(
return changesInfo return changesInfo
} }
protected open fun additionalProcessChangedClass(kotlinClass: LocalFileKotlinClass, isPackage: Boolean) = CompilationResult.NO_CHANGES
private fun CompilationResult.logIfSomethingChanged(className: JvmClassName) { private fun CompilationResult.logIfSomethingChanged(className: JvmClassName) {
if (this == CompilationResult.NO_CHANGES) return if (this == CompilationResult.NO_CHANGES) return
@@ -287,15 +259,19 @@ class IncrementalCacheImpl(
multifileClassFacadeMap.remove(it) multifileClassFacadeMap.remove(it)
multifileClassPartMap.remove(it) multifileClassPartMap.remove(it)
constantsMap.remove(it) constantsMap.remove(it)
inlineFunctionsMap.remove(it)
} }
additionalProcessRemovedClasses(dirtyClasses)
removeAllFromClassStorage(dirtyClasses) removeAllFromClassStorage(dirtyClasses)
dirtyOutputClassesMap.clean() dirtyOutputClassesMap.clean()
return changesInfo return changesInfo
} }
protected open fun additionalProcessRemovedClasses(dirtyClasses: List<JvmClassName>) {
}
override fun getObsoletePackageParts(): Collection<String> { override fun getObsoletePackageParts(): Collection<String> {
val obsoletePackageParts = val obsoletePackageParts =
dirtyOutputClassesMap.getDirtyOutputClasses().filter { packagePartMap.isPackagePart(JvmClassName.byInternalName(it)) } dirtyOutputClassesMap.getDirtyOutputClasses().filter { packagePartMap.isPackagePart(JvmClassName.byInternalName(it)) }
@@ -450,88 +426,6 @@ class IncrementalCacheImpl(
value.dumpMap(Any::toString) value.dumpMap(Any::toString)
} }
private inner class InlineFunctionsMap(storageFile: File) : BasicStringMap<Map<String, Long>>(storageFile, StringToLongMapExternalizer) {
private fun getInlineFunctionsMap(bytes: ByteArray): Map<String, Long> {
val result = HashMap<String, Long>()
val inlineFunctions = inlineFunctionsJvmNames(bytes)
if (inlineFunctions.isEmpty()) return emptyMap()
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
val dummyClassWriter = ClassWriter(Opcodes.ASM5)
return object : MethodVisitor(Opcodes.ASM5, dummyClassWriter.visitMethod(0, name, desc, null, exceptions)) {
override fun visitEnd() {
val jvmName = name + desc
if (jvmName !in inlineFunctions) return
val dummyBytes = dummyClassWriter.toByteArray()!!
val hash = dummyBytes.md5()
result[jvmName] = hash
}
}
}
}, 0)
return result
}
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult {
return put(kotlinClass.className, getInlineFunctionsMap(kotlinClass.fileContents), isPackage)
}
private fun put(className: JvmClassName, newMap: Map<String, Long>, isPackage: Boolean): CompilationResult {
val internalName = className.internalName
val oldMap = storage[internalName] ?: emptyMap()
val added = hashSetOf<String>()
val changed = hashSetOf<String>()
val allFunctions = oldMap.keys + newMap.keys
for (fn in allFunctions) {
val oldHash = oldMap[fn]
val newHash = newMap[fn]
when {
oldHash == null -> added.add(fn)
oldHash != newHash -> changed.add(fn)
}
}
when {
newMap.isNotEmpty() -> storage[internalName] = newMap
else -> storage.remove(internalName)
}
if (changed.isNotEmpty()) {
dirtyInlineFunctionsMap.put(className, changed.toList())
}
val changes =
if (IncrementalCompilation.isExperimental()) {
val fqName = if (isPackage) className.packageFqName else className.fqNameForClassNameWithoutDollars
// TODO get name in better way instead of using substringBefore
(added.asSequence() + changed.asSequence()).map { ChangeInfo.MembersChanged(fqName, listOf(it.substringBefore("("))) }
}
else {
emptySequence<ChangeInfo>()
}
return CompilationResult(inlineChanged = changed.isNotEmpty(),
inlineAdded = added.isNotEmpty(),
changes = changes)
}
fun remove(className: JvmClassName) {
storage.remove(className.internalName)
}
override fun dumpValue(value: Map<String, Long>): String =
value.dumpMap { java.lang.Long.toHexString(it) }
}
private inner class PackagePartMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) { private inner class PackagePartMap(storageFile: File) : BasicStringMap<Boolean>(storageFile, BooleanDataDescriptor.INSTANCE) {
fun addPackagePart(className: JvmClassName) { fun addPackagePart(className: JvmClassName) {
storage[className.internalName] = true storage[className.internalName] = true
@@ -662,44 +556,6 @@ class IncrementalCacheImpl(
override fun dumpValue(value: Boolean) = "" override fun dumpValue(value: Boolean) = ""
} }
private inner class DirtyInlineFunctionsMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer) {
fun getEntries(): Map<JvmClassName, Collection<String>> =
storage.keys.toMapBy(JvmClassName::byInternalName) { storage[it]!! }
fun put(className: JvmClassName, changedFunctions: List<String>) {
storage[className.internalName] = changedFunctions
}
override fun dumpValue(value: Collection<String>) = value.dumpCollection()
}
/**
* Mapping: (sourceFile+inlineFunction)->(targetFiles)
*
* Where:
* * sourceFile - path to some kotlin source
* * inlineFunction - jvmSignature of some inline function in source file
* * target files - collection of files inlineFunction has been inlined to
*/
private inner class InlineFunctionsFilesMap(storageFile: File) : BasicMap<PathFunctionPair, Collection<String>>(storageFile, PathFunctionPairKeyDescriptor, PathCollectionExternalizer) {
fun add(sourcePath: String, jvmSignature: String, targetPath: String) {
val key = PathFunctionPair(sourcePath, jvmSignature)
storage.append(key, targetPath)
}
operator fun get(sourcePath: String, jvmSignature: String): Collection<String> {
val key = PathFunctionPair(sourcePath, jvmSignature)
return storage[key] ?: emptySet()
}
override fun dumpKey(key: PathFunctionPair): String =
"(${key.path}, ${key.function})"
override fun dumpValue(value: Collection<String>) =
value.dumpCollection()
}
} }
sealed class ChangeInfo(val fqName: FqName) { sealed class ChangeInfo(val fqName: FqName) {
@@ -738,23 +594,7 @@ data class CompilationResult(
changes + other.changes) changes + other.changes)
} }
private class KotlinIncrementalStorageProvider( fun ByteArray.md5(): Long {
private val target: ModuleBuildTarget,
private val paths: BuildDataPaths
) : StorageProvider<IncrementalCacheImpl>() {
override fun equals(other: Any?) = other is KotlinIncrementalStorageProvider && target == other.target
override fun hashCode() = target.hashCode()
override fun createStorage(targetDataDir: File): IncrementalCacheImpl =
IncrementalCacheImpl(target, paths)
}
fun BuildDataManager.getKotlinCache(target: ModuleBuildTarget): IncrementalCacheImpl =
getStorage(target, KotlinIncrementalStorageProvider(target, dataPaths))
private fun ByteArray.md5(): Long {
val d = MessageDigest.getInstance("MD5").digest(this)!! val d = MessageDigest.getInstance("MD5").digest(this)!!
return ((d[0].toLong() and 0xFFL) return ((d[0].toLong() and 0xFFL)
or ((d[1].toLong() and 0xFFL) shl 8) or ((d[1].toLong() and 0xFFL) shl 8)
@@ -768,7 +608,7 @@ private fun ByteArray.md5(): Long {
} }
@TestOnly @TestOnly
private fun <K : Comparable<K>, V> Map<K, V>.dumpMap(dumpValue: (V)->String): String = fun <K : Comparable<K>, V> Map<K, V>.dumpMap(dumpValue: (V)->String): String =
buildString { buildString {
append("{") append("{")
for (key in keys.sorted()) { for (key in keys.sorted()) {
@@ -0,0 +1,216 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jps.incremental
import com.intellij.openapi.util.io.FileUtil
import gnu.trove.THashSet
import org.jetbrains.jps.builders.storage.BuildDataPaths
import org.jetbrains.jps.builders.storage.StorageProvider
import org.jetbrains.jps.incremental.ModuleBuildTarget
import org.jetbrains.jps.incremental.storage.BuildDataManager
import org.jetbrains.jps.incremental.storage.StorageOwner
import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.jps.incremental.storage.*
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.*
import java.io.File
import java.util.*
class JpsIncrementalCacheImpl(
target: ModuleBuildTarget,
paths: BuildDataPaths
) : IncrementalCacheImpl(target, paths), StorageOwner {
private val inlineFunctionsMap = registerMap(InlineFunctionsMap(INLINE_FUNCTIONS.storageFile))
private val dirtyInlineFunctionsMap = registerMap(DirtyInlineFunctionsMap(DIRTY_INLINE_FUNCTIONS.storageFile))
private val inlinedTo = registerMap(InlineFunctionsFilesMap(INLINED_TO.storageFile))
override fun registerInline(fromPath: String, jvmSignature: String, toPath: String) {
if (!IncrementalCompilation.isExperimental()) {
inlinedTo.add(fromPath, jvmSignature, toPath)
}
}
override fun additionalProcessChangedClass(kotlinClass: LocalFileKotlinClass, isPackage: Boolean) =
inlineFunctionsMap.process(kotlinClass, isPackage)
override fun additionalProcessRemovedClasses(dirtyClasses: List<JvmClassName>) {
dirtyClasses.forEach { inlineFunctionsMap.remove(it) }
}
fun getFilesToReinline(): Collection<File> {
val result = THashSet(FileUtil.PATH_HASHING_STRATEGY)
for ((className, functions) in dirtyInlineFunctionsMap.getEntries()) {
val classFilePath = getClassFilePath(className.internalName)
for (cache in dependentsWithThis) {
val targetFiles = functions.flatMap { (cache as JpsIncrementalCacheImpl).inlinedTo[classFilePath, it] }
result.addAll(targetFiles)
}
}
return result.map { File(it) }
}
fun cleanDirtyInlineFunctions() {
dirtyInlineFunctionsMap.clean()
}
private inner class InlineFunctionsMap(storageFile: File) : BasicStringMap<Map<String, Long>>(storageFile, StringToLongMapExternalizer) {
private fun getInlineFunctionsMap(bytes: ByteArray): Map<String, Long> {
val result = HashMap<String, Long>()
val inlineFunctions = inlineFunctionsJvmNames(bytes)
if (inlineFunctions.isEmpty()) return emptyMap()
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.ASM5) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
val dummyClassWriter = ClassWriter(Opcodes.ASM5)
return object : MethodVisitor(Opcodes.ASM5, dummyClassWriter.visitMethod(0, name, desc, null, exceptions)) {
override fun visitEnd() {
val jvmName = name + desc
if (jvmName !in inlineFunctions) return
val dummyBytes = dummyClassWriter.toByteArray()!!
val hash = dummyBytes.md5()
result[jvmName] = hash
}
}
}
}, 0)
return result
}
fun process(kotlinClass: LocalFileKotlinClass, isPackage: Boolean): CompilationResult {
return put(kotlinClass.className, getInlineFunctionsMap(kotlinClass.fileContents), isPackage)
}
private fun put(className: JvmClassName, newMap: Map<String, Long>, isPackage: Boolean): CompilationResult {
val internalName = className.internalName
val oldMap = storage[internalName] ?: emptyMap()
val added = hashSetOf<String>()
val changed = hashSetOf<String>()
val allFunctions = oldMap.keys + newMap.keys
for (fn in allFunctions) {
val oldHash = oldMap[fn]
val newHash = newMap[fn]
when {
oldHash == null -> added.add(fn)
oldHash != newHash -> changed.add(fn)
}
}
when {
newMap.isNotEmpty() -> storage[internalName] = newMap
else -> storage.remove(internalName)
}
if (changed.isNotEmpty()) {
dirtyInlineFunctionsMap.put(className, changed.toList())
}
val changes =
if (IncrementalCompilation.isExperimental()) {
val fqName = if (isPackage) className.packageFqName else className.fqNameForClassNameWithoutDollars
// TODO get name in better way instead of using substringBefore
(added.asSequence() + changed.asSequence()).map { ChangeInfo.MembersChanged(fqName, listOf(it.substringBefore("("))) }
}
else {
emptySequence<ChangeInfo>()
}
return CompilationResult(inlineChanged = changed.isNotEmpty(),
inlineAdded = added.isNotEmpty(),
changes = changes)
}
fun remove(className: JvmClassName) {
storage.remove(className.internalName)
}
override fun dumpValue(value: Map<String, Long>): String =
value.dumpMap { java.lang.Long.toHexString(it) }
}
private inner class DirtyInlineFunctionsMap(storageFile: File) : BasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer) {
fun getEntries(): Map<JvmClassName, Collection<String>> =
storage.keys.toMapBy(JvmClassName::byInternalName) { storage[it]!! }
fun put(className: JvmClassName, changedFunctions: List<String>) {
storage[className.internalName] = changedFunctions
}
override fun dumpValue(value: Collection<String>) = value.dumpCollection()
}
/**
* Mapping: (sourceFile+inlineFunction)->(targetFiles)
*
* Where:
* * sourceFile - path to some kotlin source
* * inlineFunction - jvmSignature of some inline function in source file
* * target files - collection of files inlineFunction has been inlined to
*/
private inner class InlineFunctionsFilesMap(storageFile: File) : BasicMap<PathFunctionPair, Collection<String>>(storageFile, PathFunctionPairKeyDescriptor, PathCollectionExternalizer) {
fun add(sourcePath: String, jvmSignature: String, targetPath: String) {
val key = PathFunctionPair(sourcePath, jvmSignature)
storage.append(key, targetPath)
}
operator fun get(sourcePath: String, jvmSignature: String): Collection<String> {
val key = PathFunctionPair(sourcePath, jvmSignature)
return storage[key] ?: emptySet()
}
override fun dumpKey(key: PathFunctionPair): String =
"(${key.path}, ${key.function})"
override fun dumpValue(value: Collection<String>) =
value.dumpCollection()
}
companion object {
private val INLINE_FUNCTIONS = "inline-functions"
private val DIRTY_INLINE_FUNCTIONS = "dirty-inline-functions"
private val INLINED_TO = "inlined-to"
}
}
private class KotlinIncrementalStorageProvider(
private val target: ModuleBuildTarget,
private val paths: BuildDataPaths
) : StorageProvider<JpsIncrementalCacheImpl>() {
override fun equals(other: Any?) = other is KotlinIncrementalStorageProvider && target == other.target
override fun hashCode() = target.hashCode()
override fun createStorage(targetDataDir: File): JpsIncrementalCacheImpl =
JpsIncrementalCacheImpl(target, paths)
}
fun BuildDataManager.getKotlinCache(target: ModuleBuildTarget): JpsIncrementalCacheImpl =
getStorage(target, KotlinIncrementalStorageProvider(target, dataPaths))