All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.ir.backend.js.ic.IncrementalCache.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * 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.ir.backend.js.ic

import org.jetbrains.kotlin.backend.common.serialization.IdSignatureDeserializer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrFragmentAndBinaryAst
import org.jetbrains.kotlin.ir.util.IdSignature
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 java.io.File


class IncrementalCache(private val library: KotlinLibrary, cachePath: String) {
    companion object {
        private const val CACHE_HEADER = "ic.header.bin"

        private const val BINARY_AST_SUFFIX = "ast.bin"
        private const val METADATA_SUFFIX = "metadata.bin"
    }

    private var forceRebuildJs = false
    private val cacheDir = File(cachePath)
    private val signatureToIndexMappingFromMetadata = mutableMapOf>()

    private val libraryFile = KotlinLibraryFile(library)

    class CacheHeader(val klibFileHash: ICHash = ICHash(), val configHash: ICHash = ICHash()) {
        fun toProtoStream(out: CodedOutputStream) {
            klibFileHash.toProtoStream(out)
            configHash.toProtoStream(out)
        }

        companion object {
            fun fromProtoStream(input: CodedInputStream): CacheHeader {
                val klibFileHash = ICHash.fromProtoStream(input)
                val configHash = ICHash.fromProtoStream(input)
                return CacheHeader(klibFileHash, configHash)
            }
        }
    }

    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))
        }
    } ?: emptyMap()

    private val kotlinLibraryHeader: KotlinLibraryHeader by lazy { KotlinLibraryHeader(library) }

    private class KotlinSourceFileMetadataFromDisk(
        override val inverseDependencies: KotlinSourceFileMap>,
        override val directDependencies: KotlinSourceFileMap>,

        override val importedInlineFunctions: Map
    ) : KotlinSourceFileMetadata()

    private object KotlinSourceFileMetadataNotExist : KotlinSourceFileMetadata() {
        override val inverseDependencies = KotlinSourceFileMap>(emptyMap())
        override val directDependencies = KotlinSourceFileMap>(emptyMap())

        override val importedInlineFunctions = emptyMap()
    }

    private val kotlinLibrarySourceFileMetadata = mutableMapOf()

    private fun KotlinSourceFile.getCacheFile(suffix: String) = File(cacheDir, "${File(path).name}.${path.stringHashForIC()}.$suffix")

    private fun commitCacheHeader(fingerprints: List>) = File(cacheDir, CACHE_HEADER).useCodedOutput {
        cacheHeader.toProtoStream(this)
        writeInt32NoTag(fingerprints.size)
        for ((srcFile, fingerprint) in fingerprints) {
            srcFile.toProtoStream(this)
            fingerprint.toProtoStream(this)
        }
    }

    fun buildModuleArtifactAndCommitCache(
        moduleName: String,
        rebuiltFileFragments: Map,
        signatureToIndexMapping: Map>
    ): ModuleArtifact {
        val fileArtifacts = kotlinLibraryHeader.sourceFiles.map { srcFile ->
            val binaryAstFile = srcFile.getCacheFile(BINARY_AST_SUFFIX)
            val rebuiltFileFragment = rebuiltFileFragments[srcFile]
            if (rebuiltFileFragment != null) {
                binaryAstFile.apply { recreate() }.writeBytes(rebuiltFileFragment.binaryAst)
            }

            commitSourceFileMetadata(srcFile, signatureToIndexMapping[srcFile] ?: emptyMap())
            SrcFileArtifact(srcFile.path, rebuiltFileFragment?.fragment, binaryAstFile)
        }

        return ModuleArtifact(moduleName, fileArtifacts, cacheDir, forceRebuildJs)
    }

    data class ModifiedFiles(
        val modified: Map = emptyMap(),
        val removed: Map = emptyMap(),
        val newFiles: Set = emptySet()
    )

    fun collectModifiedFiles(configHash: ICHash): ModifiedFiles {
        val klibFileHash = library.libraryFile.javaFile().fileHashForIC()
        cacheHeader = when {
            cacheHeader.configHash != configHash -> {
                cacheDir.deleteRecursively()
                CacheHeader(klibFileHash, configHash)
            }
            cacheHeader.klibFileHash != klibFileHash -> CacheHeader(klibFileHash, configHash)
            else -> return ModifiedFiles()
        }

        val cachedFingerprints = loadCachedFingerprints()
        val deletedFiles = cachedFingerprints.keys.toMutableSet()
        val newFiles = mutableSetOf()

        val newFingerprints = kotlinLibraryHeader.sourceFiles.mapIndexed { index, file -> file to library.fingerprint(index) }
        val modifiedFiles = buildMap(newFingerprints.size) {
            for ((file, fileNewFingerprint) in newFingerprints) {
                val oldFingerprint = cachedFingerprints[file]
                if (oldFingerprint == null) {
                    newFiles += file
                }
                if (oldFingerprint != fileNewFingerprint) {
                    val metadata = fetchSourceFileMetadata(file, false)
                    put(file, metadata)
                }
                deletedFiles.remove(file)
            }
        }

        val removedFilesMetadata = deletedFiles.associateWith {
            val metadata = fetchSourceFileMetadata(it, false)
            it.getCacheFile(BINARY_AST_SUFFIX).delete()
            it.getCacheFile(METADATA_SUFFIX).delete()
            metadata
        }

        forceRebuildJs = deletedFiles.isNotEmpty()
        commitCacheHeader(newFingerprints)

        return ModifiedFiles(modifiedFiles, removedFilesMetadata, newFiles)
    }

    fun fetchSourceFileFullMetadata(srcFile: KotlinSourceFile): KotlinSourceFileMetadata {
        return fetchSourceFileMetadata(srcFile, true)
    }

    fun updateSourceFileMetadata(srcFile: KotlinSourceFile, sourceFileMetadata: KotlinSourceFileMetadata) {
        kotlinLibrarySourceFileMetadata[srcFile] = sourceFileMetadata
    }

    private fun fetchSourceFileMetadata(srcFile: KotlinSourceFile, loadSignatures: Boolean) =
        kotlinLibrarySourceFileMetadata.getOrPut(srcFile) {
            val signatureToIndexMapping = signatureToIndexMappingFromMetadata.getOrPut(srcFile) { mutableMapOf() }
            fun IdSignatureDeserializer.deserializeIdSignatureAndSave(index: Int): IdSignature {
                val signature = deserializeIdSignature(index)
                signatureToIndexMapping[signature] = index
                return signature
            }

            val deserializer: IdSignatureDeserializer by lazy {
                kotlinLibraryHeader.signatureDeserializers[srcFile] ?: notFoundIcError("signature deserializer", libraryFile, srcFile)
            }

            srcFile.getCacheFile(METADATA_SUFFIX).useCodedInputIfExists {
                fun readDependencies() = buildMapUntil(readInt32()) {
                    val libraryFile = KotlinLibraryFile.fromProtoStream(this@useCodedInputIfExists)
                    val depends = buildMapUntil(readInt32()) {
                        val dependencySrcFile = KotlinSourceFile.fromProtoStream(this@useCodedInputIfExists)
                        val dependencySignatures = if (loadSignatures) {
                            buildSetUntil(readInt32()) { add(deserializer.deserializeIdSignatureAndSave(readInt32())) }
                        } else {
                            repeat(readInt32()) { readInt32() }
                            emptySet()
                        }
                        put(dependencySrcFile, dependencySignatures)
                    }
                    put(libraryFile, depends)
                }

                val directDependencies = KotlinSourceFileMap(readDependencies())
                val reverseDependencies = KotlinSourceFileMap(readDependencies())

                val importedInlineFunctions = if (loadSignatures) {
                    buildMapUntil(readInt32()) {
                        val signature = deserializer.deserializeIdSignatureAndSave(readInt32())
                        val transitiveHash = ICHash.fromProtoStream(this@useCodedInputIfExists)
                        put(signature, transitiveHash)
                    }
                } else {
                    emptyMap()
                }

                KotlinSourceFileMetadataFromDisk(reverseDependencies, directDependencies, importedInlineFunctions)
            } ?: KotlinSourceFileMetadataNotExist
        }

    private fun commitSourceFileMetadata(srcFile: KotlinSourceFile, signatureToIndexMapping: Map) {
        val headerCacheFile = srcFile.getCacheFile(METADATA_SUFFIX)
        val sourceFileMetadata = kotlinLibrarySourceFileMetadata[srcFile] ?: notFoundIcError("metadata", libraryFile, srcFile)
        if (sourceFileMetadata.isEmpty()) {
            headerCacheFile.delete()
            return
        }
        if (sourceFileMetadata is KotlinSourceFileMetadataFromDisk) {
            return
        }

        val signatureToIndexMappingSaved = signatureToIndexMappingFromMetadata[srcFile] ?: emptyMap()
        fun serializeSignature(signature: IdSignature): Int {
            val index = signatureToIndexMapping[signature] ?: signatureToIndexMappingSaved[signature]
            return index ?: notFoundIcError("signature $signature", libraryFile, srcFile)
        }

        headerCacheFile.useCodedOutput {
            fun writeDepends(depends: KotlinSourceFileMap>) {
                writeInt32NoTag(depends.size)
                for ((dependencyLibFile, dependencySrcFiles) in depends) {
                    dependencyLibFile.toProtoStream(this)
                    writeInt32NoTag(dependencySrcFiles.size)
                    for ((dependencySrcFile, signatures) in dependencySrcFiles) {
                        dependencySrcFile.toProtoStream(this)
                        writeInt32NoTag(signatures.size)
                        for (signature in signatures) {
                            writeInt32NoTag(serializeSignature(signature))
                        }
                    }
                }
            }

            writeDepends(sourceFileMetadata.directDependencies)
            writeDepends(sourceFileMetadata.inverseDependencies)

            writeInt32NoTag(sourceFileMetadata.importedInlineFunctions.size)
            for ((signature, transitiveHash) in sourceFileMetadata.importedInlineFunctions) {
                writeInt32NoTag(serializeSignature(signature))
                transitiveHash.toProtoStream(this)
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy