[K/N] Incremental compilation of per-file caches

This commit is contained in:
Igor Chevdar
2023-03-09 10:40:46 +02:00
committed by Space Team
parent 74864ba1d0
commit e4f30589a4
10 changed files with 309 additions and 92 deletions
@@ -167,6 +167,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
)
var autoCacheDir: String? = null
@Argument(
value = INCREMENTAL_CACHE_DIR,
valueDescription = "<path>",
description = "Path to the directory where to put incremental build caches",
delimiter = ""
)
var incrementalCacheDir: String? = null
@Argument(value="-Xcheck-dependencies", deprecatedName = "--check_dependencies", description = "Check dependencies and download the missing ones")
var checkDependencies: Boolean = false
@@ -485,6 +493,7 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
const val INCLUDE_ARG = "-Xinclude"
const val CACHED_LIBRARY = "-Xcached-library"
const val ADD_CACHE = "-Xadd-cache"
const val INCREMENTAL_CACHE_DIR = "-Xic-cache-dir"
const val SHORT_MODULE_NAME_ARG = "-Xshort-module-name"
}
}
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.common.serialization.FingerprintHash
import org.jetbrains.kotlin.backend.common.serialization.SerializedIrFileFingerprint
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.konan.file.File
@@ -13,6 +15,7 @@ import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.metadata.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.backend.konan.descriptors.isInteropLibrary
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.library.unresolvedDependencies
internal fun KotlinLibrary.getAllTransitiveDependencies(allLibraries: Map<String, KotlinLibrary>): List<KotlinLibrary> {
@@ -32,85 +35,256 @@ internal fun KotlinLibrary.getAllTransitiveDependencies(allLibraries: Map<String
return allDependencies.toList()
}
// TODO: deleteRecursively might throw an exception!
class CacheBuilder(
val konanConfig: KonanConfig,
val spawnCompilation: (List<String>, CompilerConfiguration.() -> Unit) -> Unit
) {
private val configuration = konanConfig.configuration
private val autoCacheableFrom = configuration.get(KonanConfigKeys.AUTO_CACHEABLE_FROM)!!.map { File(it) }
private val icEnabled = configuration.get(CommonConfigurationKeys.INCREMENTAL_COMPILATION)!!
private val includedLibraries = configuration.get(KonanConfigKeys.INCLUDED_LIBRARIES).orEmpty().toSet()
private val generateTestRunner = configuration.getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER)
fun needToBuild() = konanConfig.isFinalBinary && konanConfig.ignoreCacheReason == null && autoCacheableFrom.isNotEmpty()
fun needToBuild() = konanConfig.isFinalBinary && konanConfig.ignoreCacheReason == null && (autoCacheableFrom.isNotEmpty() || icEnabled)
private val allLibraries by lazy { konanConfig.resolvedLibraries.getFullList(TopologicalLibraryOrder) }
private val uniqueNameToLibrary by lazy { allLibraries.associateBy { it.uniqueName } }
private val caches = mutableMapOf<KotlinLibrary, CachedLibraries.Cache>()
private val cacheRootDirectories = mutableMapOf<KotlinLibrary, String>()
// If libA depends on libB, then dependableLibraries[libB] contains libA.
private val dependableLibraries = mutableMapOf<KotlinLibrary, MutableList<KotlinLibrary>>()
private fun findAllDependable(libraries: List<KotlinLibrary>): Set<KotlinLibrary> {
val visited = mutableSetOf<KotlinLibrary>()
fun dfs(library: KotlinLibrary) {
visited.add(library)
dependableLibraries[library]?.forEach {
if (it !in visited) dfs(it)
}
}
libraries.forEach { if (it !in visited) dfs(it) }
return visited
}
private data class LibraryFile(val library: KotlinLibrary, val file: String) {
override fun toString() = "${library.uniqueName}|$file"
}
private val KotlinLibrary.isExternal
get() = autoCacheableFrom.any { libraryFile.absolutePath.startsWith(it.absolutePath) }
fun build() {
val allLibraries = konanConfig.resolvedLibraries.getFullList(TopologicalLibraryOrder)
val uniqueNameToLibrary = allLibraries.associateBy { it.uniqueName }
val caches = mutableMapOf<KotlinLibrary, String>()
val externalLibrariesToCache = mutableListOf<KotlinLibrary>()
val icedLibraries = mutableListOf<KotlinLibrary>()
allLibraries.forEach librariesLoop@{ library ->
konanConfig.cachedLibraries.getLibraryCache(library)?.let {
caches[library] = it.rootDirectory
return@librariesLoop
allLibraries.forEach { library ->
val isDefaultOrExternal = library.isDefault || library.isExternal
val cache = konanConfig.cachedLibraries.getLibraryCache(library, !isDefaultOrExternal)
cache?.let {
caches[library] = it
cacheRootDirectories[library] = it.rootDirectory
}
val libraryPath = library.libraryFile.absolutePath
val isLibraryAutoCacheable = library.isDefault || autoCacheableFrom.any { libraryPath.startsWith(it.absolutePath) }
if (!isLibraryAutoCacheable)
return@librariesLoop
if (isDefaultOrExternal) {
if (cache == null) externalLibrariesToCache += library
} else {
icedLibraries += library
}
library.unresolvedDependencies.forEach {
val dependency = uniqueNameToLibrary[it.path]!!
dependableLibraries.getOrPut(dependency) { mutableListOf() }.add(library)
}
}
val dependencies = library.getAllTransitiveDependencies(uniqueNameToLibrary)
val dependencyCaches = dependencies.map {
caches[it] ?: run {
configuration.report(CompilerMessageSeverity.LOGGING,
"SKIPPING ${library.libraryName} as some of the dependencies aren't cached")
return@librariesLoop
}
externalLibrariesToCache.forEach { buildLibraryCache(it, true, emptyList()) }
if (!icEnabled) return
// Every library dependable on one of the changed external libraries needs its cache to be fully rebuilt.
val needFullRebuild = findAllDependable(externalLibrariesToCache)
val libraryFilesWithFqNames = mutableMapOf<KotlinLibrary, List<FileWithFqName>>()
val changedFiles = mutableListOf<LibraryFile>()
val removedFiles = mutableListOf<LibraryFile>()
val addedFiles = mutableListOf<LibraryFile>()
val reversedPerFileDependencies = mutableMapOf<LibraryFile, MutableList<LibraryFile>>()
val reversedWholeLibraryDependencies = mutableMapOf<KotlinLibrary, MutableList<LibraryFile>>()
for (library in icedLibraries) {
if (library in needFullRebuild) continue
val cache = caches[library] ?: continue
if (cache !is CachedLibraries.Cache.PerFile) {
require(library.isInteropLibrary())
continue
}
// TODO: Uncomment after making per-file caches default.
val makePerFileCache = false//!library.isInteropLibrary()
configuration.report(CompilerMessageSeverity.LOGGING, "CACHING ${library.libraryName}")
val libraryCacheDirectory = if (library.isDefault)
konanConfig.systemCacheDirectory
else
CachedLibraries.computeVersionedCacheDirectory(konanConfig.autoCacheDirectory, library, uniqueNameToLibrary)
val libraryCache = libraryCacheDirectory.child(
if (makePerFileCache)
CachedLibraries.getPerFileCachedLibraryName(library)
else
CachedLibraries.getCachedLibraryName(library)
)
try {
// TODO: Run monolithic cache builds in parallel.
libraryCacheDirectory.mkdirs()
spawnCompilation(konanConfig.additionalCacheFlags /* TODO: Some way to put them directly to CompilerConfiguration? */) {
val libraries = dependencies.filter { !it.isDefault }.map { it.libraryFile.absolutePath }
val cachedLibraries = dependencies.zip(dependencyCaches).associate { it.first.libraryFile.absolutePath to it.second }
configuration.report(CompilerMessageSeverity.LOGGING, " dependencies:\n " +
libraries.joinToString("\n "))
configuration.report(CompilerMessageSeverity.LOGGING, " caches used:\n " +
cachedLibraries.entries.joinToString("\n ") { "${it.key}: ${it.value}" })
configuration.report(CompilerMessageSeverity.LOGGING, " cache dir: " +
libraryCacheDirectory.absolutePath)
val libraryCacheRootDir = File(cache.path)
val cachedFiles = libraryCacheRootDir.listFiles.map { it.name }
setupCommonOptionsForCaches(konanConfig)
put(KonanConfigKeys.PRODUCE, CompilerOutputKind.STATIC_CACHE)
put(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE, libraryPath)
put(KonanConfigKeys.NODEFAULTLIBS, true)
put(KonanConfigKeys.NOENDORSEDLIBS, true)
put(KonanConfigKeys.NOSTDLIB, true)
put(KonanConfigKeys.LIBRARY_FILES, libraries)
put(KonanConfigKeys.CACHED_LIBRARIES, cachedLibraries)
put(KonanConfigKeys.CACHE_DIRECTORIES, listOf(libraryCacheDirectory.absolutePath))
put(KonanConfigKeys.MAKE_PER_FILE_CACHE, makePerFileCache)
val actualFilesWithFqNames = library.getFilesWithFqNames()
libraryFilesWithFqNames[library] = actualFilesWithFqNames
val actualFiles = actualFilesWithFqNames.withIndex()
.associate { CacheSupport.cacheFileId(it.value.fqName, it.value.filePath) to it.index }
.toMutableMap()
for (cachedFile in cachedFiles) {
val libraryFile = LibraryFile(library, cachedFile)
val fileIndex = actualFiles[cachedFile]
if (fileIndex == null) {
removedFiles.add(libraryFile)
} else {
actualFiles.remove(cachedFile)
val actualContentHash = SerializedIrFileFingerprint(library, fileIndex).fileFingerprint
val previousContentHash = FingerprintHash.fromByteArray(cache.getFileHash(cachedFile))
if (previousContentHash != actualContentHash)
changedFiles.add(libraryFile)
val dependencies = cache.getFileDependencies(cachedFile)
for (dependency in dependencies) {
val dependentLibrary = uniqueNameToLibrary[dependency.libName]
?: error("Unknown dependent library ${dependency.libName}")
when (val kind = dependency.kind) {
is DependenciesTracker.DependencyKind.WholeModule ->
reversedWholeLibraryDependencies.getOrPut(dependentLibrary) { mutableListOf() }.add(libraryFile)
is DependenciesTracker.DependencyKind.CertainFiles ->
kind.files.forEach {
reversedPerFileDependencies.getOrPut(LibraryFile(dependentLibrary, it)) { mutableListOf() }.add(libraryFile)
}
}
}
}
caches[library] = libraryCache.absolutePath
} catch (t: Throwable) {
configuration.report(CompilerMessageSeverity.LOGGING, "${t.message}\n${t.stackTraceToString()}")
configuration.report(CompilerMessageSeverity.WARNING,
"Failed to build cache: ${t.message}\n${t.stackTraceToString()}\n" +
"Falling back to not use cache for ${library.libraryName}")
}
for (newFile in actualFiles.keys)
addedFiles.add(LibraryFile(library, newFile))
}
libraryCache.deleteRecursively()
configuration.report(CompilerMessageSeverity.LOGGING, "IC analysis results")
configuration.report(CompilerMessageSeverity.LOGGING, " CACHED:")
icedLibraries.filter { caches[it] != null }.forEach { configuration.report(CompilerMessageSeverity.LOGGING, " ${it.libraryName}") }
configuration.report(CompilerMessageSeverity.LOGGING, " CLEAN BUILD:")
icedLibraries.filter { caches[it] == null }.forEach { configuration.report(CompilerMessageSeverity.LOGGING, " ${it.libraryName}") }
configuration.report(CompilerMessageSeverity.LOGGING, " FULL REBUILD:")
icedLibraries.filter { it in needFullRebuild }.forEach { configuration.report(CompilerMessageSeverity.LOGGING, " ${it.libraryName}") }
configuration.report(CompilerMessageSeverity.LOGGING, " ADDED FILES:")
addedFiles.forEach { configuration.report(CompilerMessageSeverity.LOGGING, " $it") }
configuration.report(CompilerMessageSeverity.LOGGING, " REMOVED FILES:")
removedFiles.forEach { configuration.report(CompilerMessageSeverity.LOGGING, " $it") }
configuration.report(CompilerMessageSeverity.LOGGING, " CHANGED FILES:")
changedFiles.forEach { configuration.report(CompilerMessageSeverity.LOGGING, " $it") }
val dirtyFiles = mutableSetOf<LibraryFile>()
fun dfs(libraryFile: LibraryFile) {
dirtyFiles += libraryFile
reversedPerFileDependencies[libraryFile]?.forEach {
if (it !in dirtyFiles) dfs(it)
}
}
removedFiles.forEach {
if (it !in dirtyFiles) dfs(it)
}
changedFiles.forEach {
if (it !in dirtyFiles) dfs(it)
}
dirtyFiles.addAll(addedFiles)
removedFiles.forEach {
dirtyFiles.remove(it)
File(caches[it.library]!!.rootDirectory).child(it.file).deleteRecursively()
}
val groupedDirtyFiles = dirtyFiles.groupBy { it.library }
configuration.report(CompilerMessageSeverity.LOGGING, " DIRTY FILES:")
groupedDirtyFiles.values.flatten().forEach {
configuration.report(CompilerMessageSeverity.LOGGING, " $it")
}
for (library in icedLibraries) {
val filesToCache = groupedDirtyFiles[library]?.let { libraryFiles ->
val filesWithFqNames = libraryFilesWithFqNames[library]!!.associateBy {
CacheSupport.cacheFileId(it.fqName, it.filePath)
}
libraryFiles.map { filesWithFqNames[it.file]!!.filePath }
}.orEmpty()
when {
library in needFullRebuild -> buildLibraryCache(library, false, emptyList())
caches[library] == null || filesToCache.isNotEmpty() -> buildLibraryCache(library, false, filesToCache)
}
}
}
private fun buildLibraryCache(library: KotlinLibrary, isExternal: Boolean, filesToCache: List<String>) {
val dependencies = library.getAllTransitiveDependencies(uniqueNameToLibrary)
val dependencyCaches = dependencies.map {
cacheRootDirectories[it] ?: run {
configuration.report(CompilerMessageSeverity.LOGGING,
"SKIPPING ${library.libraryName} as some of the dependencies aren't cached")
return
}
}
configuration.report(CompilerMessageSeverity.LOGGING, "CACHING ${library.libraryName}")
filesToCache.forEach { configuration.report(CompilerMessageSeverity.LOGGING, " $it") }
// Produce monolithic caches for external libraries for now.
val makePerFileCache = !isExternal && !library.isInteropLibrary()
val libraryCacheDirectory = when {
library.isDefault -> konanConfig.systemCacheDirectory
isExternal -> CachedLibraries.computeVersionedCacheDirectory(konanConfig.autoCacheDirectory, library, uniqueNameToLibrary)
else -> konanConfig.incrementalCacheDirectory!!
}
val libraryCache = libraryCacheDirectory.child(
if (makePerFileCache)
CachedLibraries.getPerFileCachedLibraryName(library)
else
CachedLibraries.getCachedLibraryName(library)
)
try {
// TODO: Run monolithic cache builds in parallel.
libraryCacheDirectory.mkdirs()
spawnCompilation(konanConfig.additionalCacheFlags /* TODO: Some way to put them directly to CompilerConfiguration? */) {
val libraryPath = library.libraryFile.absolutePath
val libraries = dependencies.filter { !it.isDefault }.map { it.libraryFile.absolutePath }
val cachedLibraries = dependencies.zip(dependencyCaches).associate { it.first.libraryFile.absolutePath to it.second }
configuration.report(CompilerMessageSeverity.LOGGING, " dependencies:\n " +
libraries.joinToString("\n "))
configuration.report(CompilerMessageSeverity.LOGGING, " caches used:\n " +
cachedLibraries.entries.joinToString("\n ") { "${it.key}: ${it.value}" })
configuration.report(CompilerMessageSeverity.LOGGING, " cache dir: " +
libraryCacheDirectory.absolutePath)
setupCommonOptionsForCaches(konanConfig)
put(KonanConfigKeys.PRODUCE, CompilerOutputKind.STATIC_CACHE)
put(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE, libraryPath)
put(KonanConfigKeys.NODEFAULTLIBS, true)
put(KonanConfigKeys.NOENDORSEDLIBS, true)
put(KonanConfigKeys.NOSTDLIB, true)
put(KonanConfigKeys.LIBRARY_FILES, libraries)
if (generateTestRunner != TestRunnerKind.NONE && libraryPath in includedLibraries) {
put(KonanConfigKeys.GENERATE_TEST_RUNNER, generateTestRunner)
put(KonanConfigKeys.INCLUDED_LIBRARIES, listOf(libraryPath))
}
put(KonanConfigKeys.CACHED_LIBRARIES, cachedLibraries)
put(KonanConfigKeys.CACHE_DIRECTORIES, listOf(libraryCacheDirectory.absolutePath))
put(KonanConfigKeys.MAKE_PER_FILE_CACHE, makePerFileCache)
if (filesToCache.isNotEmpty())
put(KonanConfigKeys.FILES_TO_CACHE, filesToCache)
}
cacheRootDirectories[library] = libraryCache.absolutePath
} catch (t: Throwable) {
configuration.report(CompilerMessageSeverity.LOGGING, "${t.message}\n${t.stackTraceToString()}")
configuration.report(CompilerMessageSeverity.WARNING,
"Failed to build cache: ${t.message}\n${t.stackTraceToString()}\n" +
"Falling back to not use cache for ${library.libraryName}")
libraryCache.deleteRecursively()
}
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.serialization.ClassFieldsSerializer
import org.jetbrains.kotlin.backend.konan.serialization.EagerInitializedPropertySerializer
import org.jetbrains.kotlin.backend.konan.serialization.InlineFunctionBodyReferenceSerializer
import org.jetbrains.kotlin.konan.file.File
internal class CacheStorage(private val generationState: NativeGenerationState) {
private val outputFiles = generationState.outputFiles
@@ -16,8 +17,10 @@ internal class CacheStorage(private val generationState: NativeGenerationState)
fun renameOutput(outputFiles: OutputFiles) {
// For caches the output file is a directory. It might be created by someone else,
// we have to delete it in order for the next renaming operation to succeed.
// TODO: what if the directory is not empty?
java.io.File(outputFiles.mainFileName).delete()
val tempDirectoryForRemoval = File(outputFiles.mainFileName + "-to-remove")
if (outputFiles.mainFile.exists && !outputFiles.mainFile.renameTo(tempDirectoryForRemoval))
return
tempDirectoryForRemoval.deleteRecursively()
if (!outputFiles.tempCacheDirectory!!.renameTo(outputFiles.mainFile))
outputFiles.tempCacheDirectory.deleteRecursively()
}
@@ -80,10 +80,11 @@ class PartialCacheInfo(val klib: KotlinLibrary, val strategy: CacheDeserializati
class CacheSupport(
private val configuration: CompilerConfiguration,
resolvedLibraries: KotlinLibraryResolveResult,
private val resolvedLibraries: KotlinLibraryResolveResult,
ignoreCacheReason: String?,
systemCacheDirectory: File,
autoCacheDirectory: File,
incrementalCacheDirectory: File?,
target: KonanTarget,
val produce: CompilerOutputKind
) {
@@ -103,7 +104,9 @@ class CacheSupport(
add(File(it).takeIf { it.isDirectory }
?: configuration.reportCompilationError("cache directory $it is not found or is not a directory"))
}
systemCacheDirectory.takeIf { autoCacheableFrom.isNotEmpty() }?.let { add(it) }
systemCacheDirectory.takeIf { autoCacheableFrom.isNotEmpty() || incrementalCacheDirectory != null }?.let { add(it) }
autoCacheDirectory.takeIf { autoCacheableFrom.isNotEmpty() }?.let { add(it) }
incrementalCacheDirectory?.let { add(it) }
}
internal fun tryGetImplicitOutput(cacheDeserializationStrategy: CacheDeserializationStrategy?): String? {
@@ -178,10 +181,11 @@ class CacheSupport(
configuration.get(KonanConfigKeys.PRE_LINK_CACHES, false)
companion object {
fun cacheFileId(fqName: String, filePath: String) = "${if (fqName == "") "ROOT" else fqName}.${filePath.hashCode().toString(Character.MAX_RADIX)}"
fun cacheFileId(fqName: String, filePath: String) =
"${if (fqName == "") "ROOT" else fqName}.${filePath.hashCode().toString(Character.MAX_RADIX)}"
}
init {
fun checkConsistency() {
// Ensure dependencies of every cached library are cached too:
resolvedLibraries.getFullList { libraries ->
libraries.map { library ->
@@ -84,11 +84,13 @@ class CachedLibraries(
}
}
class PerFile(target: KonanTarget, kind: Kind, path: String, private val fileDirs: List<File>)
class PerFile(target: KonanTarget, kind: Kind, path: String, fileDirs: List<File>, val complete: Boolean)
: Cache(target, kind, path, File(path).absolutePath)
{
private val existingFileDirs = if (complete) fileDirs else fileDirs.filter { it.exists }
private val perFileBitcodeDependencies by lazy {
fileDirs.associate {
existingFileDirs.associate {
val data = it.child(PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME).child(BITCODE_DEPENDENCIES_FILE_NAME).readStrings()
it.name to DependenciesSerializer.deserialize(it.absolutePath, data)
}
@@ -108,26 +110,26 @@ class CachedLibraries(
override fun computeBitcodeDependencies() = perFileBitcodeDependencies.values.flatten()
override fun computeBinariesPaths() = fileDirs.map {
override fun computeBinariesPaths() = existingFileDirs.map {
it.child(PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME).child(getArtifactName(target, it.name, kind.toCompilerOutputKind())).absolutePath
}
override fun computeSerializedInlineFunctionBodies() = mutableListOf<SerializedInlineFunctionReference>().also {
fileDirs.forEach { fileDir ->
existingFileDirs.forEach { fileDir ->
val data = fileDir.child(PER_FILE_CACHE_IR_LEVEL_DIR_NAME).child(INLINE_FUNCTION_BODIES_FILE_NAME).readBytes()
InlineFunctionBodyReferenceSerializer.deserializeTo(data, it)
}
}
override fun computeSerializedClassFields() = mutableListOf<SerializedClassFields>().also {
fileDirs.forEach { fileDir ->
existingFileDirs.forEach { fileDir ->
val data = fileDir.child(PER_FILE_CACHE_IR_LEVEL_DIR_NAME).child(CLASS_FIELDS_FILE_NAME).readBytes()
ClassFieldsSerializer.deserializeTo(data, it)
}
}
override fun computeSerializedEagerInitializedFiles() = mutableListOf<SerializedEagerInitializedFile>().also {
fileDirs.forEach { fileDir ->
existingFileDirs.forEach { fileDir ->
val data = fileDir.child(PER_FILE_CACHE_IR_LEVEL_DIR_NAME).child(EAGER_INITIALIZED_PROPERTIES_FILE_NAME).readBytes()
EagerInitializedPropertySerializer.deserializeTo(data, it)
}
@@ -162,8 +164,8 @@ class CachedLibraries(
val libraryFileDirs = librariesFileDirs.getOrPut(library) {
library.getFilesWithFqNames().map { cacheDir.child(CacheSupport.cacheFileId(it.fqName, it.filePath)) }
}
Cache.PerFile(target, Kind.STATIC, cacheDir.absolutePath, libraryFileDirs)
.takeIf { cacheDirContents.containsAll(libraryFileDirs.map { it.absolutePath }) }
Cache.PerFile(target, Kind.STATIC, cacheDir.absolutePath, libraryFileDirs,
complete = cacheDirContents.containsAll(libraryFileDirs.map { it.absolutePath }))
}
}
}
@@ -196,8 +198,8 @@ class CachedLibraries(
fun isLibraryCached(library: KotlinLibrary): Boolean =
getLibraryCache(library) != null
fun getLibraryCache(library: KotlinLibrary): Cache? =
allCaches[library]
fun getLibraryCache(library: KotlinLibrary, allowIncomplete: Boolean = false): Cache? =
allCaches[library]?.takeIf { allowIncomplete || (it as? Cache.PerFile)?.complete != false }
val hasStaticCaches = allCaches.values.any {
when (it.kind) {
@@ -406,6 +406,8 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
internal val additionalCacheFlags by lazy { platformManager.loader(target).additionalCacheFlags }
internal val threadsCount = configuration.get(CommonConfigurationKeys.PARALLEL_BACKEND_THREADS) ?: 1
private fun StringBuilder.appendCommonCacheFlavor() {
append(target.toString())
if (debug) append("-g")
@@ -440,9 +442,19 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
}
private val systemCacheRootDirectory = File(distribution.konanHome).child("klib").child("cache")
internal val systemCacheDirectory = systemCacheRootDirectory.child(systemCacheFlavorString)
private val autoCacheRootDirectory = configuration.get(KonanConfigKeys.AUTO_CACHE_DIR)?.let { File(it) } ?: systemCacheRootDirectory
internal val autoCacheDirectory = autoCacheRootDirectory.child(userCacheFlavorString)
internal val systemCacheDirectory = systemCacheRootDirectory.child(systemCacheFlavorString).also { it.mkdirs() }
private val autoCacheRootDirectory = configuration.get(KonanConfigKeys.AUTO_CACHE_DIR)?.let {
File(it).apply {
if (!isDirectory) configuration.reportCompilationError("auto cache directory $this is not found or is not a directory")
}
} ?: systemCacheRootDirectory
internal val autoCacheDirectory = autoCacheRootDirectory.child(userCacheFlavorString).also { it.mkdirs() }
private val incrementalCacheRootDirectory = configuration.get(KonanConfigKeys.INCREMENTAL_CACHE_DIR)?.let {
File(it).apply {
if (!isDirectory) configuration.reportCompilationError("incremental cache directory $this is not found or is not a directory")
}
}
internal val incrementalCacheDirectory = incrementalCacheRootDirectory?.child(userCacheFlavorString)?.also { it.mkdirs() }
internal val ignoreCacheReason = when {
optimizationsEnabled -> "for optimized compilation"
@@ -457,6 +469,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
ignoreCacheReason = ignoreCacheReason,
systemCacheDirectory = systemCacheDirectory,
autoCacheDirectory = autoCacheDirectory,
incrementalCacheDirectory = incrementalCacheDirectory,
target = target,
produce = produce
)
@@ -39,6 +39,8 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create<List<String>>("paths to the root directories from which dependencies are to be cached automatically")
val AUTO_CACHE_DIR: CompilerConfigurationKey<String>
= CompilerConfigurationKey.create<String>("path to the directory where to put caches for auto-cacheable dependencies")
val INCREMENTAL_CACHE_DIR: CompilerConfigurationKey<String>
= CompilerConfigurationKey.create<String>("path to the directory where to put incremental build caches")
val CACHED_LIBRARIES: CompilerConfigurationKey<Map<String, String>>
= CompilerConfigurationKey.create<Map<String, String>>("mapping from library paths to cache paths")
val FILES_TO_CACHE: CompilerConfigurationKey<List<String>>
@@ -40,14 +40,17 @@ class KonanDriver(
) {
fun run() {
val fileNames = configuration.get(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE)?.let { libPath ->
if (configuration.get(KonanConfigKeys.MAKE_PER_FILE_CACHE) != true)
configuration.get(KonanConfigKeys.FILES_TO_CACHE)
else {
val lib = createKonanLibrary(File(libPath), "default", null, true)
(0 until lib.fileCount()).map { fileIndex ->
val proto = IrFile.parseFrom(lib.file(fileIndex).codedInputStream, ExtensionRegistryLite.newInstance())
proto.fileEntry.name
val filesToCache = configuration.get(KonanConfigKeys.FILES_TO_CACHE)
when {
!filesToCache.isNullOrEmpty() -> filesToCache
configuration.get(KonanConfigKeys.MAKE_PER_FILE_CACHE) == true -> {
val lib = createKonanLibrary(File(libPath), "default", null, true)
(0 until lib.fileCount()).map { fileIndex ->
val proto = IrFile.parseFrom(lib.file(fileIndex).codedInputStream, ExtensionRegistryLite.newInstance())
proto.fileEntry.name
}
}
else -> null
}
}
if (fileNames != null) {
@@ -75,6 +78,8 @@ class KonanDriver(
konanConfig = KonanConfig(project, configuration) // TODO: Just set freshly built caches.
}
konanConfig.cacheSupport.checkConsistency()
DynamicCompilerDriver().run(konanConfig, environment)
}
@@ -177,6 +177,11 @@ fun CompilerConfiguration.setupFromArguments(arguments: K2NativeCompilerArgument
put(CACHE_DIRECTORIES, arguments.cacheDirectories.toNonNullList())
put(AUTO_CACHEABLE_FROM, arguments.autoCacheableFrom.toNonNullList())
arguments.autoCacheDir?.let { put(AUTO_CACHE_DIR, it) }
val incrementalCacheDir = arguments.incrementalCacheDir
if ((incrementalCacheDir != null) xor (arguments.incrementalCompilation == true))
report(ERROR, "For incremental compilation both flags should be supplied: " +
"-Xenable-incremental-compilation and ${K2NativeCompilerArguments.INCREMENTAL_CACHE_DIR}")
incrementalCacheDir?.let { put(INCREMENTAL_CACHE_DIR, it) }
arguments.filesToCache?.let { put(FILES_TO_CACHE, it.toList()) }
put(MAKE_PER_FILE_CACHE, arguments.makePerFileCache)
val nThreadsRaw = parseBackendThreads(arguments.backendThreads)
@@ -302,6 +307,7 @@ internal fun CompilerConfiguration.setupCommonOptionsForCaches(konanConfig: Kona
put(BinaryOptions.freezing, konanConfig.freezing)
put(BinaryOptions.runtimeAssertionsMode, konanConfig.runtimeAssertsMode)
put(LAZY_IR_FOR_CACHES, konanConfig.lazyIrForCaches)
put(CommonConfigurationKeys.PARALLEL_BACKEND_THREADS, konanConfig.threadsCount)
}
private fun Array<String>?.toNonNullList() = this?.asList().orEmpty()
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.backend.konan.driver.utilities.CExportFiles
import org.jetbrains.kotlin.backend.konan.driver.utilities.createTempFiles
import org.jetbrains.kotlin.backend.konan.ir.konanLibrary
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -119,8 +118,8 @@ internal fun <C : PhaseContext> PhaseEngine<C>.runBackend(backendContext: Contex
}
val fragments = backendEngine.splitIntoFragments(irModule)
val nThreads = context.config.configuration.get(CommonConfigurationKeys.PARALLEL_BACKEND_THREADS) ?: 1
if (nThreads == 1) {
val threadsCount = context.config.threadsCount
if (threadsCount == 1) {
fragments.forEach { fragment ->
runAfterLowerings(fragment, createGenerationStateAndRunLowerings(fragment))
}
@@ -133,9 +132,9 @@ internal fun <C : PhaseContext> PhaseEngine<C>.runBackend(backendContext: Contex
// We'd love to run entire pipeline in parallel, but it's difficult (mainly because of the lowerings,
// which need cross-file access all the time and it's not easy to overcome this). So, for now,
// we split the pipeline into two parts - everything before lowerings (including them)
// which is run sequentially, and everything else which in run in parallel.
// which is run sequentially, and everything else which is run in parallel.
val generationStates = fragmentsList.map { fragment -> createGenerationStateAndRunLowerings(fragment) }
val executor = Executors.newFixedThreadPool(nThreads)
val executor = Executors.newFixedThreadPool(threadsCount)
val thrownFromThread = AtomicReference<Throwable?>(null)
val tasks = fragmentsList.zip(generationStates).map { (fragment, generationState) ->
Callable {