[IC] Segregate LazyStorage into LazyStorage and AppendableLazyStorage

#KT-56052 In Progress
This commit is contained in:
Alexander.Likhachev
2023-02-21 17:13:11 +01:00
committed by Space Team
parent cef557c407
commit 99830d7f28
11 changed files with 142 additions and 61 deletions
@@ -24,22 +24,11 @@ import org.jetbrains.kotlin.incremental.IncrementalCompilationContext
import org.jetbrains.kotlin.utils.Printer import org.jetbrains.kotlin.utils.Printer
import java.io.File import java.io.File
abstract class BasicMap<K : Comparable<K>, V>( abstract class BasicMap<K : Comparable<K>, V, StorageType : LazyStorage<K, V>>(
internal val storageFile: File, internal val storageFile: File,
keyDescriptor: KeyDescriptor<K>, protected val storage: StorageType,
valueExternalizer: DataExternalizer<V>,
protected val icContext: IncrementalCompilationContext, protected val icContext: IncrementalCompilationContext,
) { ) {
protected val storage: LazyStorage<K, V> = CachingLazyStorage(storageFile, keyDescriptor, valueExternalizer).let {
if (icContext.keepIncrementalCompilationCachesInMemory) {
DefaultInMemoryStorageWrapper(it).also { wrapper ->
icContext.transaction.registerInMemoryStorageWrapper(wrapper)
}
} else {
it
}
}
protected val pathConverter protected val pathConverter
get() = icContext.pathConverter get() = icContext.pathConverter
@@ -86,12 +75,34 @@ abstract class BasicMap<K : Comparable<K>, V>(
protected abstract fun dumpValue(value: V): String protected abstract fun dumpValue(value: V): String
} }
abstract class NonAppendableBasicMap<K : Comparable<K>, V>(
storageFile: File,
keyDescriptor: KeyDescriptor<K>,
valueExternalizer: DataExternalizer<V>,
icContext: IncrementalCompilationContext,
) : BasicMap<K, V, LazyStorage<K, V>>(
storageFile,
createLazyStorage(storageFile, keyDescriptor, valueExternalizer, icContext),
icContext
)
abstract class AppendableBasicMap<K : Comparable<K>, V>(
storageFile: File,
keyDescriptor: KeyDescriptor<K>,
valueExternalizer: AppendableDataExternalizer<V>,
icContext: IncrementalCompilationContext,
) : BasicMap<K, V, AppendableLazyStorage<K, V>>(
storageFile,
createLazyStorage(storageFile, keyDescriptor, valueExternalizer, icContext),
icContext
)
abstract class BasicStringMap<V>( abstract class BasicStringMap<V>(
storageFile: File, storageFile: File,
keyDescriptor: KeyDescriptor<String>, keyDescriptor: KeyDescriptor<String>,
valueExternalizer: DataExternalizer<V>, valueExternalizer: DataExternalizer<V>,
icContext: IncrementalCompilationContext, icContext: IncrementalCompilationContext,
) : BasicMap<String, V>(storageFile, keyDescriptor, valueExternalizer, icContext) { ) : NonAppendableBasicMap<String, V>(storageFile, keyDescriptor, valueExternalizer, icContext) {
constructor( constructor(
storageFile: File, storageFile: File,
valueExternalizer: DataExternalizer<V>, valueExternalizer: DataExternalizer<V>,
@@ -100,3 +111,18 @@ abstract class BasicStringMap<V>(
override fun dumpKey(key: String): String = key override fun dumpKey(key: String): String = key
} }
abstract class AppendableBasicStringMap<V>(
storageFile: File,
keyDescriptor: KeyDescriptor<String>,
valueExternalizer: AppendableDataExternalizer<V>,
icContext: IncrementalCompilationContext,
) : AppendableBasicMap<String, V>(storageFile, keyDescriptor, valueExternalizer, icContext) {
constructor(
storageFile: File,
valueExternalizer: AppendableDataExternalizer<V>,
icContext: IncrementalCompilationContext,
) : this(storageFile, EnumeratorStringDescriptor.INSTANCE, valueExternalizer, icContext)
override fun dumpKey(key: String): String = key
}
@@ -21,7 +21,7 @@ import java.io.File
import java.io.IOException import java.io.IOException
open class BasicMapsOwner(val cachesDir: File) { open class BasicMapsOwner(val cachesDir: File) {
private val maps = arrayListOf<BasicMap<*, *>>() private val maps = arrayListOf<BasicMap<*, *, *>>()
companion object { companion object {
val CACHE_EXTENSION = "tab" val CACHE_EXTENSION = "tab"
@@ -31,17 +31,17 @@ open class BasicMapsOwner(val cachesDir: File) {
get() = File(cachesDir, this + "." + CACHE_EXTENSION) get() = File(cachesDir, this + "." + CACHE_EXTENSION)
@Synchronized @Synchronized
protected fun <K, V, M : BasicMap<K, V>> registerMap(map: M): M { protected fun <K, V, S, M : BasicMap<K, V, S>> registerMap(map: M): M {
maps.add(map) maps.add(map)
return map return map
} }
open fun clean() { open fun clean() {
forEachMapSafe("clean", BasicMap<*, *>::clean) forEachMapSafe("clean", BasicMap<*, *, *>::clean)
} }
open fun close() { open fun close() {
forEachMapSafe("close", BasicMap<*, *>::close) forEachMapSafe("close", BasicMap<*, *, *>::close)
} }
open fun flush(memoryCachesOnly: Boolean) { open fun flush(memoryCachesOnly: Boolean) {
@@ -49,7 +49,7 @@ open class BasicMapsOwner(val cachesDir: File) {
} }
@Synchronized @Synchronized
private fun forEachMapSafe(actionName: String, action: (BasicMap<*, *>) -> Unit) { private fun forEachMapSafe(actionName: String, action: (BasicMap<*, *, *>) -> Unit) {
val actionExceptions = LinkedHashMap<String, Exception>() val actionExceptions = LinkedHashMap<String, Exception>()
maps.forEach { maps.forEach {
try { try {
@@ -20,6 +20,7 @@ import com.intellij.util.io.DataExternalizer
import com.intellij.util.io.IOUtil import com.intellij.util.io.IOUtil
import com.intellij.util.io.KeyDescriptor import com.intellij.util.io.KeyDescriptor
import com.intellij.util.io.PersistentHashMap import com.intellij.util.io.PersistentHashMap
import org.jetbrains.kotlin.incremental.IncrementalCompilationContext
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -31,7 +32,7 @@ class CachingLazyStorage<K, V>(
private val storageFile: File, private val storageFile: File,
private val keyDescriptor: KeyDescriptor<K>, private val keyDescriptor: KeyDescriptor<K>,
private val valueExternalizer: DataExternalizer<V> private val valueExternalizer: DataExternalizer<V>
) : LazyStorage<K, V> { ) : AppendableLazyStorage<K, V> {
private var storage: PersistentHashMap<K, V>? = null private var storage: PersistentHashMap<K, V>? = null
private var isStorageFileExist = true private var isStorageFileExist = true
@@ -80,6 +81,9 @@ class CachingLazyStorage<K, V>(
@Synchronized @Synchronized
override fun append(key: K, value: V) { override fun append(key: K, value: V) {
check(valueExternalizer is AppendableDataExternalizer<*>) {
"`valueExternalizer` should implement the `AppendableDataExternalizer` interface to be able to call `append`"
}
getStorageOrCreateNew().appendData(key, { valueExternalizer.save(it, value) }) getStorageOrCreateNew().appendData(key, { valueExternalizer.save(it, value) })
} }
@@ -119,3 +123,32 @@ class CachingLazyStorage<K, V>(
private fun createMap(): PersistentHashMap<K, V> = PersistentHashMap(storageFile, keyDescriptor, valueExternalizer) private fun createMap(): PersistentHashMap<K, V> = PersistentHashMap(storageFile, keyDescriptor, valueExternalizer)
} }
private fun <K, V> createLazyStorageImpl(
storageFile: File,
keyDescriptor: KeyDescriptor<K>,
valueExternalizer: DataExternalizer<V>,
icContext: IncrementalCompilationContext,
) = CachingLazyStorage(storageFile, keyDescriptor, valueExternalizer).let {
if (icContext.keepIncrementalCompilationCachesInMemory) {
DefaultInMemoryStorageWrapper(it, valueExternalizer).also { wrapper ->
icContext.transaction.registerInMemoryStorageWrapper(wrapper)
}
} else {
it
}
}
fun <K, V> createLazyStorage(
storageFile: File,
keyDescriptor: KeyDescriptor<K>,
valueExternalizer: DataExternalizer<V>,
icContext: IncrementalCompilationContext,
): LazyStorage<K, V> = createLazyStorageImpl(storageFile, keyDescriptor, valueExternalizer, icContext)
fun <K, V> createLazyStorage(
storageFile: File,
keyDescriptor: KeyDescriptor<K>,
valueExternalizer: AppendableDataExternalizer<V>,
icContext: IncrementalCompilationContext,
): AppendableLazyStorage<K, V> = createLazyStorageImpl(storageFile, keyDescriptor, valueExternalizer, icContext)
@@ -24,7 +24,7 @@ import java.io.File
internal open class ClassOneToManyMap( internal open class ClassOneToManyMap(
storageFile: File, storageFile: File,
icContext: IncrementalCompilationContext, icContext: IncrementalCompilationContext,
) : BasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer, icContext) { ) : AppendableBasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer, icContext) {
override fun dumpValue(value: Collection<String>): String = value.dumpCollection() override fun dumpValue(value: Collection<String>): String = value.dumpCollection()
@Synchronized @Synchronized
@@ -24,7 +24,7 @@ import java.io.File
internal class IdToFileMap( internal class IdToFileMap(
file: File, file: File,
icContext: IncrementalCompilationContext, icContext: IncrementalCompilationContext,
) : BasicMap<Int, String>(file, ExternalIntegerKeyDescriptor(), EnumeratorStringDescriptor.INSTANCE, icContext) { ) : NonAppendableBasicMap<Int, String>(file, ExternalIntegerKeyDescriptor(), EnumeratorStringDescriptor.INSTANCE, icContext) {
override fun dumpKey(key: Int): String = key.toString() override fun dumpKey(key: Int): String = key.toString()
override fun dumpValue(value: String): String = value override fun dumpValue(value: String): String = value
@@ -5,10 +5,11 @@
package org.jetbrains.kotlin.incremental.storage package org.jetbrains.kotlin.incremental.storage
import com.intellij.util.io.DataExternalizer
import java.util.* import java.util.*
import kotlin.collections.LinkedHashMap import kotlin.collections.LinkedHashMap
interface InMemoryStorageWrapper<K, V> : LazyStorage<K, V> { interface InMemoryStorageWrapper<K, V> : AppendableLazyStorage<K, V> {
fun resetInMemoryChanges() fun resetInMemoryChanges()
} }
@@ -17,7 +18,12 @@ interface InMemoryStorageWrapper<K, V> : LazyStorage<K, V> {
* Flushes all the changes to the [origin] on [flush] invocation. * Flushes all the changes to the [origin] on [flush] invocation.
* [resetInMemoryChanges] should be called to reset in-memory changes of this wrapper. * [resetInMemoryChanges] should be called to reset in-memory changes of this wrapper.
*/ */
class DefaultInMemoryStorageWrapper<K, V>(private val origin: LazyStorage<K, V>) : InMemoryStorageWrapper<K, V> { class DefaultInMemoryStorageWrapper<K, V>(
private val origin: CachingLazyStorage<K, V>,
private val valueExternalizer: DataExternalizer<V>
) :
InMemoryStorageWrapper<K, V> {
// These state properties keep the current diff that will be applied to the [origin] on flush if [resetInMemoryChanges] is not called
private val inMemoryStorage = LinkedHashMap<K, ValueWrapper<V>>() private val inMemoryStorage = LinkedHashMap<K, ValueWrapper<V>>()
private val removedKeys = hashSetOf<K>() private val removedKeys = hashSetOf<K>()
private var isCleanRequested = false private var isCleanRequested = false
@@ -65,25 +71,21 @@ class DefaultInMemoryStorageWrapper<K, V>(private val origin: LazyStorage<K, V>)
} }
override fun append(key: K, value: V) { override fun append(key: K, value: V) {
@Suppress("UNCHECKED_CAST") check(valueExternalizer is AppendableDataExternalizer<V>) {
when (value) { "`valueExternalizer` should implement the `AppendableDataExternalizer` interface to be able to call `append`"
is Collection<*> -> { }
when { when {
key in inMemoryStorage -> { key in inMemoryStorage -> {
// the key's value was set in-memory, so we keep changing it without additional marking as append val currentEntry = inMemoryStorage.getValue(key)
(inMemoryStorage.getValue(key).value as MutableCollection<Any?>).addAll(value) // should we avoid new allocations by performing appends in-place?
} inMemoryStorage[key] = ValueWrapper(valueExternalizer.append(currentEntry.value, value), isAppend = currentEntry.isAppend)
key !in removedKeys && key in origin -> { }
val collection = copyCollection(origin[key] as Collection<*>) key !in removedKeys && key in origin -> {
collection.addAll(value) inMemoryStorage[key] = ValueWrapper(value, isAppend = true)
inMemoryStorage[key] = ValueWrapper(collection as V, isAppend = true) }
} else -> {
else -> { set(key, value)
set(key, value)
}
}
} }
else -> error("The value is expected to implement the Collection interface")
} }
} }
@@ -93,23 +95,26 @@ class DefaultInMemoryStorageWrapper<K, V>(private val origin: LazyStorage<K, V>)
} }
override fun set(key: K, value: V) { override fun set(key: K, value: V) {
@Suppress("UNCHECKED_CAST") inMemoryStorage[key] = ValueWrapper(value)
inMemoryStorage[key] = ValueWrapper(if (value is Collection<*>) copyCollection(value) as V else value)
} }
private fun copyCollection(collection: Collection<*>): MutableCollection<Any?> { override fun get(key: K): V? {
val newCollection = when (collection) { return when (key) {
is Set<*> -> TreeSet<Any?>() in inMemoryStorage -> {
else -> ArrayList<Any?>(collection.size) val entry = inMemoryStorage.getValue(key)
val originValue = origin[key]
if (entry.isAppend && originValue != null) {
check(valueExternalizer is AppendableDataExternalizer<V>) {
"`valueExternalizer` should implement the `AppendableDataExternalizer` interface to be able to handle `append`"
}
valueExternalizer.append(originValue, entry.value)
} else {
entry.value
}
}
!in removedKeys -> origin[key]
else -> null
} }
newCollection.addAll(collection)
return newCollection
}
override fun get(key: K): V? = when (key) {
in inMemoryStorage -> inMemoryStorage.getValue(key).value
!in removedKeys -> origin[key]
else -> null
} }
override fun contains(key: K) = key in inMemoryStorage || (key !in removedKeys && key in origin) override fun contains(key: K) = key in inMemoryStorage || (key !in removedKeys && key in origin)
@@ -22,8 +22,11 @@ interface LazyStorage<K, V> {
operator fun get(key: K): V? operator fun get(key: K): V?
operator fun set(key: K, value: V) operator fun set(key: K, value: V)
fun remove(key: K) fun remove(key: K)
fun append(key: K, value: V)
fun clean() fun clean()
fun flush(memoryCachesOnly: Boolean) fun flush(memoryCachesOnly: Boolean)
fun close() fun close()
}
interface AppendableLazyStorage<K, V> : LazyStorage<K, V> {
fun append(key: K, value: V)
} }
@@ -23,7 +23,7 @@ class LookupMap(
storage: File, storage: File,
icContext: IncrementalCompilationContext, icContext: IncrementalCompilationContext,
) : ) :
BasicMap<LookupSymbolKey, Collection<Int>>( AppendableBasicMap<LookupSymbolKey, Collection<Int>>(
storage, storage,
LookupSymbolKeyDescriptor(icContext.storeFullFqNamesInLookupCache), LookupSymbolKeyDescriptor(icContext.storeFullFqNamesInLookupCache),
IntCollectionExternalizer, IntCollectionExternalizer,
@@ -12,7 +12,7 @@ import java.io.File
class SourceToJsOutputMap( class SourceToJsOutputMap(
storageFile: File, storageFile: File,
icContext: IncrementalCompilationContext, icContext: IncrementalCompilationContext,
) : BasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer, icContext) { ) : AppendableBasicStringMap<Collection<String>>(storageFile, StringCollectionExternalizer, icContext) {
override fun dumpValue(value: Collection<String>): String = value.dumpCollection() override fun dumpValue(value: Collection<String>): String = value.dumpCollection()
@Synchronized @Synchronized
@@ -36,7 +36,7 @@ internal abstract class AbstractSourceToOutputMap<Name>(
private val nameTransformer: NameTransformer<Name>, private val nameTransformer: NameTransformer<Name>,
storageFile: File, storageFile: File,
icContext: IncrementalCompilationContext, icContext: IncrementalCompilationContext,
) : BasicStringMap<Collection<String>>(storageFile, PathStringDescriptor, StringCollectionExternalizer, icContext) { ) : AppendableBasicStringMap<Collection<String>>(storageFile, PathStringDescriptor, StringCollectionExternalizer, icContext) {
fun clearOutputsForSource(sourceFile: File) { fun clearOutputsForSource(sourceFile: File) {
remove(pathConverter.toPath(sourceFile)) remove(pathConverter.toPath(sourceFile))
} }
@@ -31,6 +31,18 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.* import java.io.*
/**
* Externalizer that works correctly when [PersistentHashMap.appendData] is called
*
* Besides the [append] method, it should support incremental [save] and [read]. E.g. if [save] was called multiple times, [read] should be able to collect them together
*/
interface AppendableDataExternalizer<T> : DataExternalizer<T> {
/**
* Combines two non-serialized values
*/
fun append(currentValue: T, appendData: T): T
}
class LookupSymbolKeyDescriptor( class LookupSymbolKeyDescriptor(
/** If `true`, original values are saved; if `false`, only hashes are saved. */ /** If `true`, original values are saved; if `false`, only hashes are saved. */
private val storeFullFqNames: Boolean = false private val storeFullFqNames: Boolean = false
@@ -276,7 +288,7 @@ class DelegateDataExternalizer<T>(
open class CollectionExternalizer<T>( open class CollectionExternalizer<T>(
private val elementExternalizer: DataExternalizer<T>, private val elementExternalizer: DataExternalizer<T>,
private val newCollection: () -> MutableCollection<T> private val newCollection: () -> MutableCollection<T>
) : DataExternalizer<Collection<T>> { ) : AppendableDataExternalizer<Collection<T>> {
override fun read(input: DataInput): Collection<T> { override fun read(input: DataInput): Collection<T> {
val result = newCollection() val result = newCollection()
val stream = input as DataInputStream val stream = input as DataInputStream
@@ -291,6 +303,8 @@ open class CollectionExternalizer<T>(
override fun save(output: DataOutput, value: Collection<T>) { override fun save(output: DataOutput, value: Collection<T>) {
value.forEach { elementExternalizer.save(output, it) } value.forEach { elementExternalizer.save(output, it) }
} }
override fun append(currentValue: Collection<T>, appendData: Collection<T>) = currentValue + appendData
} }
object StringCollectionExternalizer : CollectionExternalizer<String>(EnumeratorStringDescriptor(), { HashSet() }) object StringCollectionExternalizer : CollectionExternalizer<String>(EnumeratorStringDescriptor(), { HashSet() })