[JS IR] Commit cache header after lowering
Commiting cache header before lowering may break caches in case of lowering errors. ^KT-54912 Fixed
This commit is contained in:
committed by
Space Team
parent
8cde0a81bc
commit
ca19d71a00
@@ -650,7 +650,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
val cacheUpdater = CacheUpdater(
|
||||
mainModule = arguments.includes!!,
|
||||
allModules = libraries,
|
||||
icCacheRootDir = cacheDirectories.last(),
|
||||
cacheDir = cacheDirectories.last(),
|
||||
compilerConfiguration = configurationJs,
|
||||
irFactory = { IrFactoryImplForJsIC(WholeWorldStageController()) },
|
||||
mainArguments = mainCallArguments,
|
||||
@@ -666,8 +666,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
var libIndex = 0
|
||||
for ((libFile, srcFiles) in cacheUpdater.getDirtyFileLastStats()) {
|
||||
val (msg, showFiles) = when {
|
||||
srcFiles.values.all { it.contains(DirtyFileState.ADDED_FILE) } -> "fully rebuilt due to clean build" to false
|
||||
srcFiles.values.all { it.contains(DirtyFileState.MODIFIED_CONFIG) } -> "fully rebuilt due to config modification" to false
|
||||
srcFiles.values.all { it.contains(DirtyFileState.ADDED_FILE) } -> "fully rebuilt" to false
|
||||
else -> "partially rebuilt" to true
|
||||
}
|
||||
messageCollector.report(INFO, "${++libIndex}) module [${File(libFile.path).name}] was $msg")
|
||||
|
||||
@@ -37,7 +37,6 @@ fun interface JsIrCompilerICInterfaceFactory {
|
||||
|
||||
enum class DirtyFileState(val str: String) {
|
||||
ADDED_FILE("added file"),
|
||||
MODIFIED_CONFIG("modified config"),
|
||||
MODIFIED_IR("modified ir"),
|
||||
UPDATED_EXPORTS("updated exports"),
|
||||
UPDATED_IMPORTS("updated imports"),
|
||||
@@ -49,7 +48,7 @@ enum class DirtyFileState(val str: String) {
|
||||
class CacheUpdater(
|
||||
mainModule: String,
|
||||
private val allModules: Collection<String>,
|
||||
private val icCacheRootDir: String,
|
||||
cacheDir: String,
|
||||
private val compilerConfiguration: CompilerConfiguration,
|
||||
private val irFactory: () -> IrFactory,
|
||||
private val mainArguments: List<String>?,
|
||||
@@ -59,10 +58,10 @@ class CacheUpdater(
|
||||
|
||||
private val dirtyFileStats = KotlinSourceFileMutableMap<EnumSet<DirtyFileState>>()
|
||||
|
||||
private val configHash = compilerConfiguration.configHashForIC()
|
||||
|
||||
private val mainLibraryFile = KotlinLibraryFile(File(mainModule).canonicalPath)
|
||||
|
||||
private val cacheRootDir = File(cacheDir, "version.${compilerConfiguration.configHashForIC()}")
|
||||
|
||||
fun getDirtyFileLastStats(): KotlinSourceFileMap<EnumSet<DirtyFileState>> = dirtyFileStats
|
||||
|
||||
fun getStopwatchLastLaps() = stopwatch.laps
|
||||
@@ -104,12 +103,10 @@ class CacheUpdater(
|
||||
}
|
||||
}
|
||||
|
||||
private val incrementalCaches = stopwatch.measure("Incremental cache - initializing") {
|
||||
libraryDependencies.keys.associate { lib ->
|
||||
val libFile = KotlinLibraryFile(lib)
|
||||
val file = File(libFile.path)
|
||||
libFile to IncrementalCache(lib, File(icCacheRootDir, "${file.absolutePath.stringHashForIC()}.${file.name}"))
|
||||
}
|
||||
private val incrementalCaches = libraryDependencies.keys.associate { lib ->
|
||||
val libFile = KotlinLibraryFile(lib)
|
||||
val file = File(libFile.path)
|
||||
libFile to IncrementalCache(lib, File(cacheRootDir, "${file.name}.${file.absolutePath.stringHashForIC()}"))
|
||||
}
|
||||
|
||||
private fun getLibIncrementalCache(libFile: KotlinLibraryFile) =
|
||||
@@ -159,14 +156,13 @@ class CacheUpdater(
|
||||
val removedFilesMetadata = hashMapOf<KotlinLibraryFile, Map<KotlinSourceFile, KotlinSourceFileMetadata>>()
|
||||
|
||||
val modifiedFiles = KotlinSourceFileMap(incrementalCaches.entries.associate { (lib, cache) ->
|
||||
val (dirtyFiles, removedFiles, newFiles, modifiedConfigFiles) = cache.collectModifiedFiles(configHash)
|
||||
val (dirtyFiles, removedFiles, newFiles) = cache.collectModifiedFiles()
|
||||
|
||||
val fileStats by lazy(LazyThreadSafetyMode.NONE) { dirtyFileStats.getOrPutFiles(lib) }
|
||||
newFiles.forEach { fileStats.addDirtFileStat(it, DirtyFileState.ADDED_FILE) }
|
||||
modifiedConfigFiles.forEach { fileStats.addDirtFileStat(it, DirtyFileState.MODIFIED_CONFIG) }
|
||||
removedFiles.forEach { fileStats.addDirtFileStat(it.key, DirtyFileState.REMOVED_FILE) }
|
||||
dirtyFiles.forEach {
|
||||
if (it.key !in newFiles && it.key !in modifiedConfigFiles) {
|
||||
if (it.key !in newFiles) {
|
||||
fileStats.addDirtFileStat(it.key, DirtyFileState.MODIFIED_IR)
|
||||
}
|
||||
}
|
||||
|
||||
+65
-64
@@ -12,10 +12,11 @@ import org.jetbrains.kotlin.library.KotlinLibrary
|
||||
import org.jetbrains.kotlin.library.impl.javaFile
|
||||
import org.jetbrains.kotlin.protobuf.CodedInputStream
|
||||
import org.jetbrains.kotlin.protobuf.CodedOutputStream
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
|
||||
import java.io.File
|
||||
|
||||
|
||||
internal class IncrementalCache(private val library: KotlinLibrary, private val cacheDir: File) {
|
||||
internal class IncrementalCache(private val library: KotlinLibrary, val cacheDir: File) {
|
||||
companion object {
|
||||
private const val CACHE_HEADER = "ic.header.bin"
|
||||
|
||||
@@ -24,40 +25,58 @@ internal class IncrementalCache(private val library: KotlinLibrary, private val
|
||||
private const val METADATA_TMP_SUFFIX = "metadata.tmp.bin"
|
||||
}
|
||||
|
||||
private var forceRebuildJs = false
|
||||
private val signatureToIndexMappingFromMetadata = hashMapOf<KotlinSourceFile, MutableMap<IdSignature, Int>>()
|
||||
|
||||
private val libraryFile = KotlinLibraryFile(library)
|
||||
|
||||
class CacheHeader(val klibFileHash: ICHash = ICHash(), val configHash: ICHash = ICHash()) {
|
||||
private val cacheHeaderFile = File(cacheDir, CACHE_HEADER)
|
||||
|
||||
private var newCacheHeader: CacheHeader? = null
|
||||
|
||||
private var removedSrcFiles: Collection<KotlinSourceFile> = emptyList()
|
||||
|
||||
private val kotlinLibraryHeader: KotlinLibraryHeader by lazy { KotlinLibraryHeader(library) }
|
||||
|
||||
private class CacheHeader(val klibFileHash: ICHash, val klibFile: KotlinLibraryFile, val fingerprints: Map<KotlinSourceFile, ICHash>?) {
|
||||
fun toProtoStream(out: CodedOutputStream) {
|
||||
klibFile.toProtoStream(out)
|
||||
klibFileHash.toProtoStream(out)
|
||||
configHash.toProtoStream(out)
|
||||
|
||||
if (fingerprints == null) {
|
||||
notFoundIcError("fingerprints", klibFile)
|
||||
}
|
||||
out.writeInt32NoTag(fingerprints.size)
|
||||
for ((srcFile, fingerprint) in fingerprints) {
|
||||
srcFile.toProtoStream(out)
|
||||
fingerprint.toProtoStream(out)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromProtoStream(input: CodedInputStream): CacheHeader {
|
||||
val klibFileHash = ICHash.fromProtoStream(input)
|
||||
val configHash = ICHash.fromProtoStream(input)
|
||||
return CacheHeader(klibFileHash, configHash)
|
||||
fun fromProtoStream(input: CodedInputStream, newKlibFileHash: ICHash): CacheHeader {
|
||||
val klibFile = KotlinLibraryFile.fromProtoStream(input)
|
||||
val oldKlibFileHash = ICHash.fromProtoStream(input)
|
||||
|
||||
val fingerprints = (oldKlibFileHash != newKlibFileHash).ifTrue {
|
||||
buildMapUntil(input.readInt32()) {
|
||||
val file = KotlinSourceFile.fromProtoStream(input)
|
||||
put(file, ICHash.fromProtoStream(input))
|
||||
}
|
||||
}
|
||||
return CacheHeader(oldKlibFileHash, klibFile, fingerprints)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var cacheHeader = File(cacheDir, CACHE_HEADER).useCodedInputIfExists {
|
||||
CacheHeader.fromProtoStream(this)
|
||||
} ?: CacheHeader()
|
||||
|
||||
private fun loadCachedFingerprints() = File(cacheDir, CACHE_HEADER).useCodedInputIfExists {
|
||||
// skip cache header
|
||||
CacheHeader.fromProtoStream(this@useCodedInputIfExists)
|
||||
buildMapUntil(readInt32()) {
|
||||
val file = KotlinSourceFile.fromProtoStream(this@useCodedInputIfExists)
|
||||
put(file, ICHash.fromProtoStream(this@useCodedInputIfExists))
|
||||
private fun loadCacheHeader(newKlibFileHash: ICHash): CacheHeader? {
|
||||
return cacheHeaderFile.useCodedInputIfExists {
|
||||
val cacheHeader = CacheHeader.fromProtoStream(this, newKlibFileHash)
|
||||
if (cacheHeader.klibFile != libraryFile) {
|
||||
icError("broken incremental cache header $cacheHeaderFile; expected $libraryFile, got ${cacheHeader.klibFile}")
|
||||
}
|
||||
cacheHeader
|
||||
}
|
||||
} ?: emptyMap()
|
||||
|
||||
private val kotlinLibraryHeader: KotlinLibraryHeader by lazy { KotlinLibraryHeader(library) }
|
||||
}
|
||||
|
||||
private class KotlinSourceFileMetadataFromDisk(
|
||||
override val inverseDependencies: KotlinSourceFileMap<Set<IdSignature>>,
|
||||
@@ -73,53 +92,46 @@ internal class IncrementalCache(private val library: KotlinLibrary, private val
|
||||
|
||||
private fun KotlinSourceFile.getCacheFile(suffix: String) = File(cacheDir, "${File(path).name}.${path.stringHashForIC()}.$suffix")
|
||||
|
||||
private fun commitCacheHeader(fingerprints: List<Pair<KotlinSourceFile, ICHash>>) = File(cacheDir, CACHE_HEADER).useCodedOutput {
|
||||
cacheHeader.toProtoStream(this)
|
||||
writeInt32NoTag(fingerprints.size)
|
||||
for ((srcFile, fingerprint) in fingerprints) {
|
||||
srcFile.toProtoStream(this)
|
||||
fingerprint.toProtoStream(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun buildIncrementalCacheArtifact(signatureToIndexMapping: Map<KotlinSourceFile, Map<IdSignature, Int>>): IncrementalCacheArtifact {
|
||||
newCacheHeader?.let { cacheHeader ->
|
||||
cacheHeaderFile.useCodedOutput { cacheHeader.toProtoStream(this) }
|
||||
}
|
||||
|
||||
for (removedFile in removedSrcFiles) {
|
||||
removedFile.getCacheFile(BINARY_AST_SUFFIX).delete()
|
||||
removedFile.getCacheFile(METADATA_SUFFIX).delete()
|
||||
}
|
||||
|
||||
val fileArtifacts = kotlinLibraryHeader.sourceFiles.map { srcFile ->
|
||||
commitSourceFileMetadata(srcFile.getCacheFile(BINARY_AST_SUFFIX), srcFile, signatureToIndexMapping[srcFile] ?: emptyMap())
|
||||
}
|
||||
return IncrementalCacheArtifact(cacheDir, forceRebuildJs, fileArtifacts, library.jsOutputName)
|
||||
return IncrementalCacheArtifact(cacheDir, removedSrcFiles.isNotEmpty(), fileArtifacts, library.jsOutputName)
|
||||
}
|
||||
|
||||
data class ModifiedFiles(
|
||||
val dirtyFiles: Map<KotlinSourceFile, KotlinSourceFileMetadata> = emptyMap(),
|
||||
val removedFiles: Map<KotlinSourceFile, KotlinSourceFileMetadata> = emptyMap(),
|
||||
val newFiles: Set<KotlinSourceFile> = emptySet(),
|
||||
val modifiedConfigFiles: Set<KotlinSourceFile> = emptySet(),
|
||||
)
|
||||
|
||||
fun collectModifiedFiles(configHash: ICHash): ModifiedFiles {
|
||||
var isConfigModified = false
|
||||
fun collectModifiedFiles(): ModifiedFiles {
|
||||
val klibFileHash = library.libraryFile.javaFile().fileHashForIC()
|
||||
cacheHeader = when {
|
||||
cacheHeader.configHash != configHash -> {
|
||||
cacheDir.deleteRecursively()
|
||||
isConfigModified = cacheHeader.configHash != ICHash()
|
||||
CacheHeader(klibFileHash, configHash)
|
||||
}
|
||||
|
||||
cacheHeader.klibFileHash != klibFileHash -> CacheHeader(klibFileHash, configHash)
|
||||
else -> return ModifiedFiles()
|
||||
}
|
||||
val cacheHeader = loadCacheHeader(klibFileHash)
|
||||
|
||||
val cachedFingerprints = cacheHeader?.let { it.fingerprints ?: return ModifiedFiles() } ?: emptyMap()
|
||||
|
||||
val cachedFingerprints = loadCachedFingerprints()
|
||||
val deletedFiles = HashSet(cachedFingerprints.keys)
|
||||
val unknownFiles = mutableSetOf<KotlinSourceFile>()
|
||||
val newFiles = mutableSetOf<KotlinSourceFile>()
|
||||
|
||||
val newFingerprints = HashMap<KotlinSourceFile, ICHash>(kotlinLibraryHeader.sourceFiles.size)
|
||||
kotlinLibraryHeader.sourceFiles.forEachIndexed { index, file -> newFingerprints[file] = library.fingerprint(index) }
|
||||
|
||||
val newFingerprints = kotlinLibraryHeader.sourceFiles.mapIndexed { index, file -> file to library.fingerprint(index) }
|
||||
val modifiedFiles = HashMap<KotlinSourceFile, KotlinSourceFileMetadata>(newFingerprints.size).apply {
|
||||
for ((file, fileNewFingerprint) in newFingerprints) {
|
||||
val oldFingerprint = cachedFingerprints[file]
|
||||
if (oldFingerprint == null) {
|
||||
unknownFiles += file
|
||||
newFiles += file
|
||||
}
|
||||
if (oldFingerprint != fileNewFingerprint) {
|
||||
val metadata = fetchSourceFileMetadata(file, false)
|
||||
@@ -129,23 +141,12 @@ internal class IncrementalCache(private val library: KotlinLibrary, private val
|
||||
}
|
||||
}
|
||||
|
||||
val removedFilesMetadata = deletedFiles.associateWith {
|
||||
val metadata = fetchSourceFileMetadata(it, false)
|
||||
it.getCacheFile(BINARY_AST_SUFFIX).delete()
|
||||
it.getCacheFile(METADATA_SUFFIX).delete()
|
||||
metadata
|
||||
}
|
||||
val removedFiles = deletedFiles.associateWith { fetchSourceFileMetadata(it, false) }
|
||||
|
||||
forceRebuildJs = deletedFiles.isNotEmpty()
|
||||
commitCacheHeader(newFingerprints)
|
||||
removedSrcFiles = deletedFiles
|
||||
newCacheHeader = CacheHeader(klibFileHash, libraryFile, newFingerprints)
|
||||
|
||||
val (newFiles, modifiedConfigFiles) = if (isConfigModified) {
|
||||
emptySet<KotlinSourceFile>() to unknownFiles
|
||||
} else {
|
||||
unknownFiles to emptySet<KotlinSourceFile>()
|
||||
}
|
||||
|
||||
return ModifiedFiles(modifiedFiles, removedFilesMetadata, newFiles, modifiedConfigFiles)
|
||||
return ModifiedFiles(modifiedFiles, removedFiles, newFiles)
|
||||
}
|
||||
|
||||
fun fetchSourceFileFullMetadata(srcFile: KotlinSourceFile): KotlinSourceFileMetadata {
|
||||
@@ -170,12 +171,12 @@ internal class IncrementalCache(private val library: KotlinLibrary, private val
|
||||
}
|
||||
|
||||
fun <T> CodedInputStream.readDependencies(signaturesReader: () -> T) = buildMapUntil(readInt32()) {
|
||||
val libraryFile = KotlinLibraryFile.fromProtoStream(this@readDependencies)
|
||||
val libFile = KotlinLibraryFile.fromProtoStream(this@readDependencies)
|
||||
val depends = buildMapUntil(readInt32()) {
|
||||
val dependencySrcFile = KotlinSourceFile.fromProtoStream(this@readDependencies)
|
||||
put(dependencySrcFile, signaturesReader())
|
||||
}
|
||||
put(libraryFile, depends)
|
||||
put(libFile, depends)
|
||||
}
|
||||
|
||||
fun CodedInputStream.readDirectDependencies() = readDependencies {
|
||||
|
||||
@@ -229,7 +229,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
|
||||
val cacheUpdater = CacheUpdater(
|
||||
mainModule = testInfo.last().modulePath,
|
||||
allModules = testInfo.mapTo(mutableListOf(STDLIB_KLIB)) { it.modulePath },
|
||||
icCacheRootDir = buildDir.resolve("incremental-cache").absolutePath,
|
||||
cacheDir = buildDir.resolve("incremental-cache").absolutePath,
|
||||
compilerConfiguration = configuration,
|
||||
irFactory = { IrFactoryImplForJsIC(WholeWorldStageController()) },
|
||||
mainArguments = null,
|
||||
|
||||
@@ -193,7 +193,7 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
|
||||
val cacheUpdater = CacheUpdater(
|
||||
mainModule = mainModuleKlibFile.absolutePath,
|
||||
allModules = allDependencies.regularDependencies.map { it.path },
|
||||
icCacheRootDir = buildDir.resolve("libs-cache").absolutePath,
|
||||
cacheDir = buildDir.resolve("libs-cache").absolutePath,
|
||||
compilerConfiguration = configuration,
|
||||
irFactory = { IrFactoryImplForJsIC(WholeWorldStageController()) },
|
||||
mainArguments = null,
|
||||
|
||||
@@ -7,6 +7,6 @@ STEP 1:
|
||||
U : l1.1.kt -> l1.kt
|
||||
modified ir: l1.kt
|
||||
STEP 2:
|
||||
modified config: l1.kt
|
||||
added file: l1.kt
|
||||
STEP 3:
|
||||
updated exports: l1.kt
|
||||
|
||||
@@ -7,7 +7,7 @@ STEP 1:
|
||||
dependencies: lib1
|
||||
STEP 2:
|
||||
dependencies: lib1
|
||||
modified config: m.kt, testEnumEntries.kt, testEnumValues.kt
|
||||
added file: m.kt, testEnumEntries.kt, testEnumValues.kt
|
||||
STEP 3:
|
||||
modifications:
|
||||
U : testEnumEntries.3.kt -> testEnumEntries.kt
|
||||
|
||||
+12
-7
@@ -176,18 +176,23 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
|
||||
fun testJsIrIncrementalMultipleArtifacts(gradleVersion: GradleVersion) {
|
||||
project("kotlin-js-ir-ic-multiple-artifacts", gradleVersion) {
|
||||
build("compileDevelopmentExecutableKotlinJs") {
|
||||
val cacheDir = projectPath.resolve("app/build/klib/cache/lib/")
|
||||
.toFile()
|
||||
assertTrue("Lib cache size should be 2") {
|
||||
cacheDir
|
||||
.list()
|
||||
?.size == 2
|
||||
val cacheDir = projectPath.resolve("app/build/klib/cache/").toFile()
|
||||
val cacheRootDirName = cacheDir.list()?.singleOrNull()
|
||||
assertTrue("Lib cache root dir should contain 1 element 'version.hash'") {
|
||||
cacheRootDirName?.startsWith("version.") ?: false
|
||||
}
|
||||
val cacheRootDir = cacheDir.resolve(cacheRootDirName!!)
|
||||
val klibCacheDirs = cacheRootDir.list()
|
||||
// 2 for lib.klib + 1 for stdlib + 1 for main
|
||||
assertEquals(4, klibCacheDirs?.size, "cache should contain 4 dirs")
|
||||
|
||||
val libKlibCacheDirs = klibCacheDirs?.filter { dir -> dir.startsWith("lib.klib.") }
|
||||
assertEquals(2, libKlibCacheDirs?.size, "cache should contain 2 dirs for lib.klib")
|
||||
|
||||
var lib = false
|
||||
var libOther = false
|
||||
|
||||
cacheDir.listFiles()!!
|
||||
cacheRootDir.listFiles()!!
|
||||
.forEach {
|
||||
it.listFiles()!!
|
||||
.filter { it.isFile }
|
||||
|
||||
Reference in New Issue
Block a user