Skip to content

Commit

Permalink
Add initial support for producing and consuming cache
Browse files Browse the repository at this point in the history
Dynamic cache isn't fully functional on Apple targets yet.
  • Loading branch information
SvyatoslavScherbina committed Oct 9, 2019
1 parent fe591aa commit 8205a26
Show file tree
Hide file tree
Showing 16 changed files with 394 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
}

val K2NativeCompilerArguments.isUsefulWithoutFreeArgs: Boolean
get() = listTargets || listPhases || checkDependencies || !includes.isNullOrEmpty()
get() = listTargets || listPhases || checkDependencies || !includes.isNullOrEmpty() ||
!librariesToCache.isNullOrEmpty()

fun Array<String>?.toNonNullList(): List<String> {
return this?.asList<String>() ?: listOf<String>()
Expand Down Expand Up @@ -211,6 +212,10 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
put(LIBRARIES_TO_COVER, arguments.coveredLibraries.toNonNullList())
arguments.coverageFile?.let { put(PROFRAW_PATH, it) }
put(OBJC_GENERICS, arguments.objcGenerics)

put(LIBRARIES_TO_CACHE, parseLibrariesToCache(arguments, configuration, outputKind))
put(CACHE_DIRECTORIES, arguments.cacheDirectories.toNonNullList())
put(CACHED_LIBRARIES, parseCachedLibraries(arguments, configuration))
}
}
}
Expand Down Expand Up @@ -301,5 +306,36 @@ private fun selectIncludes(
}
}

private fun parseCachedLibraries(
arguments: K2NativeCompilerArguments,
configuration: CompilerConfiguration
): Map<String, String> = arguments.cachedLibraries?.asList().orEmpty().mapNotNull {
val libraryAndCache = it.split(",")
if (libraryAndCache.size != 2) {
configuration.report(
ERROR,
"incorrect $CACHED_LIBRARY format: expected '<library>,<cache>', got '$it'"
)
null
} else {
libraryAndCache[0] to libraryAndCache[1]
}
}.toMap()

private fun parseLibrariesToCache(
arguments: K2NativeCompilerArguments,
configuration: CompilerConfiguration,
outputKind: CompilerOutputKind
): List<String> {
val input = arguments.librariesToCache?.asList().orEmpty()

return if (input.isNotEmpty() && !outputKind.isCache) {
configuration.report(ERROR, "$MAKE_CACHE can't be used when not producing cache")
emptyList()
} else {
input
}
}

fun main(args: Array<String>) = K2Native.main(args)

Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
// Make sure to prepend them with -X.
// Keep the list lexically sorted.

@Argument(
value = "-Xcache-directory",
valueDescription = "<path>",
description = "Path to the directory containing caches",
delimiter = ""
)
var cacheDirectories: Array<String>? = null

@Argument(
value = CACHED_LIBRARY,
valueDescription = "<library path>,<cache path>",
description = "Comma-separated paths of a library and its cache",
delimiter = ""
)
var cachedLibraries: Array<String>? = null

@Argument(value="-Xcheck-dependencies", deprecatedName = "--check_dependencies", description = "Check dependencies and download the missing ones")
var checkDependencies: Boolean = false

Expand Down Expand Up @@ -139,6 +155,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xg0", description = "Add light debug information")
var lightDebug: Boolean = false

@Argument(
value = MAKE_CACHE,
valueDescription = "<path>",
description = "Path of the library to be compiled to cache",
delimiter = ""
)
var librariesToCache: Array<String>? = null

@Argument(value = "-Xprint-bitcode", deprecatedName = "--print_bitcode", description = "Print llvm bitcode")
var printBitCode: Boolean = false

Expand Down Expand Up @@ -220,3 +244,5 @@ const val EMBED_BITCODE_FLAG = "-Xembed-bitcode"
const val EMBED_BITCODE_MARKER_FLAG = "-Xembed-bitcode-marker"
const val STATIC_FRAMEWORK_FLAG = "-Xstatic-framework"
const val INCLUDE_ARG = "-Xinclude"
const val CACHED_LIBRARY = "-Xcached-library"
const val MAKE_CACHE = "-Xmake-cache"
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ internal class BuiltInFictitiousFunctionIrClassFactory(

class FunctionalInterface(val irClass: IrClass, val arity: Int)

fun buildAllClasses() {
val maxArity = 255 // See [BuiltInFictitiousFunctionClassFactory].
(0 .. maxArity).forEach { arity ->
function(arity)
kFunction(arity)
suspendFunction(arity)
kSuspendFunction(arity)
}
}

fun function(n: Int) = buildClass(irBuiltIns.builtIns.getFunction(n) as FunctionClassDescriptor)
fun kFunction(n: Int) = buildClass(reflectionTypes.getKFunction(n) as FunctionClassDescriptor)
fun suspendFunction(n: Int) = buildClass(irBuiltIns.builtIns.getSuspendFunction(n) as FunctionClassDescriptor)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/

package org.jetbrains.kotlin.backend.konan

import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult

class CacheSupport(
configuration: CompilerConfiguration,
resolvedLibraries: KotlinLibraryResolveResult,
target: KonanTarget,
produce: CompilerOutputKind
) {
private val allLibraries = resolvedLibraries.getFullList()

// TODO: consider using [FeaturedLibraries.kt].
private val fileToLibrary = allLibraries.associateBy { it.libraryFile }

internal val cachedLibraries: CachedLibraries = run {
val explicitCacheFiles = configuration.get(KonanConfigKeys.CACHED_LIBRARIES)!!

val explicitCaches = explicitCacheFiles.entries.associate { (libraryPath, cachePath) ->
val library = fileToLibrary[File(libraryPath)]
?: configuration.reportCompilationError("cache not applied: library $libraryPath in $cachePath")

library to cachePath
}

val implicitCacheDirectories = configuration.get(KonanConfigKeys.CACHE_DIRECTORIES)!!
.map {
File(it).takeIf { it.isDirectory }
?: configuration.reportCompilationError("cache directory $it is not found or not a directory")
}

CachedLibraries(
target = target,
allLibraries = allLibraries,
explicitCaches = explicitCaches,
implicitCacheDirectories = implicitCacheDirectories
)
}

internal val librariesToCache: Set<KotlinLibrary> = configuration.get(KonanConfigKeys.LIBRARIES_TO_CACHE)!!
.map { File(it) }.map {
fileToLibrary[it] ?: error("library to cache\n" +
" ${it.absolutePath}\n" +
"not found among resolved libraries:\n " +
allLibraries.joinToString("\n ") { it.libraryFile.absolutePath })
}.toSet()
.also { if (!produce.isCache) check(it.isEmpty()) }

init {
// Ensure dependencies of every cached library are cached too:
resolvedLibraries.getFullList { libraries ->
libraries.map { library ->
val cache = cachedLibraries.getLibraryCache(library.library)
if (cache != null || library.library in librariesToCache) {
library.resolvedDependencies.forEach {
if (!cachedLibraries.isLibraryCached(it.library) && it.library !in librariesToCache) {
val description = if (cache != null) {
"cached (in ${cache.path})"
} else {
"going to be cached"
}
configuration.reportCompilationError(
"${library.library.libraryName} is $description, " +
"but its dependency isn't: ${it.library.libraryName}"
)
}
}
}

library
}
}

// Ensure not making cache for libraries that are already cached:
librariesToCache.forEach {
val cache = cachedLibraries.getLibraryCache(it)
if (cache != null) {
configuration.reportCompilationError("Can't cache library '${it.libraryName}' " +
"that is already cached in '${cache.path}'")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/

package org.jetbrains.kotlin.backend.konan

import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult

internal class CachedLibraries(
private val target: KonanTarget,
allLibraries: List<KotlinLibrary>,
explicitCaches: Map<KotlinLibrary, String>,
implicitCacheDirectories: List<File>
) {

class Cache(val kind: Kind, val path: String) {
enum class Kind { DYNAMIC, STATIC }
}

private val allCaches: Map<KotlinLibrary, Cache> = allLibraries.mapNotNull { library ->
val explicitPath = explicitCaches[library]

val cache = if (explicitPath != null) {
val kind = when {
explicitPath.endsWith(target.family.dynamicSuffix) -> Cache.Kind.DYNAMIC
explicitPath.endsWith(target.family.staticSuffix) -> Cache.Kind.STATIC
else -> error("unexpected cache: $explicitPath")
}
Cache(kind, explicitPath)
} else {
implicitCacheDirectories.firstNotNullResult { dir ->
val baseName = "${library.uniqueName}-cache"
val dynamicFile = dir.child(getArtifactName(baseName, CompilerOutputKind.DYNAMIC_CACHE))
val staticFile = dir.child(getArtifactName(baseName, CompilerOutputKind.STATIC_CACHE))

when {
dynamicFile.exists -> Cache(Cache.Kind.DYNAMIC, dynamicFile.absolutePath)
staticFile.exists -> Cache(Cache.Kind.STATIC, staticFile.absolutePath)
else -> null
}
}
}

cache?.let { library to it }
}.toMap()

private fun getArtifactName(baseName: String, kind: CompilerOutputKind) =
"${kind.prefix(target)}$baseName${kind.suffix(target)}"

fun isLibraryCached(library: KotlinLibrary): Boolean =
getLibraryCache(library) != null

fun getLibraryCache(library: KotlinLibrary): Cache? =
allCaches[library]

val hasStaticCaches = allCaches.values.any {
when (it.kind) {
Cache.Kind.STATIC -> true
Cache.Kind.DYNAMIC -> false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ val CompilerOutputKind.involvesLinkStage: Boolean
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
}

val CompilerOutputKind.isCache: Boolean
get() = (this == CompilerOutputKind.STATIC_CACHE || this == CompilerOutputKind.DYNAMIC_CACHE)

internal fun produceCStubs(context: Context) {
val llvmModule = context.llvmModule!!
context.cStubsManager.compile(context.config.clang, context.messageCollector, context.inVerbosePhase)?.let {
Expand Down Expand Up @@ -96,6 +99,8 @@ internal fun produceOutput(context: Context) {
CompilerOutputKind.STATIC,
CompilerOutputKind.DYNAMIC,
CompilerOutputKind.FRAMEWORK,
CompilerOutputKind.DYNAMIC_CACHE,
CompilerOutputKind.STATIC_CACHE,
CompilerOutputKind.PROGRAM -> {
val output = tempFiles.nativeBinaryFileName
context.bitcodeFileName = output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ import org.jetbrains.kotlin.backend.common.serialization.KotlinMangler
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
import org.jetbrains.kotlin.backend.konan.llvm.coverage.CoverageManager
import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl
import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
import org.jetbrains.kotlin.library.SerializedIrModule
Expand Down Expand Up @@ -465,14 +464,11 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {

lateinit var compilerOutput: List<ObjectFile>

val llvmModuleSpecification: LlvmModuleSpecification = object : LlvmModuleSpecification {
// Currently all code is compiled to single LLVM module.
override fun importsKotlinDeclarationsFromOtherObjectFiles(): Boolean = false
override fun containsLibrary(library: KonanLibrary): Boolean = true
override fun containsModule(module: ModuleDescriptor): Boolean = true
override fun containsModule(module: IrModuleFragment): Boolean = true
override fun containsDeclaration(declaration: IrDeclaration): Boolean = true
}
val llvmModuleSpecification: LlvmModuleSpecification = LlvmModuleSpecificationImpl(
config.cachedLibraries,
producingCache = config.produce.isCache,
librariesToCache = config.librariesToCache
)
}

private fun MemberScope.getContributedClassifier(name: String) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.util.Logger
import kotlin.system.exitProcess
import org.jetbrains.kotlin.library.toUnresolvedLibraries
import org.jetbrains.kotlin.konan.KonanVersion
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.resolver.impl.libraryResolver
import org.jetbrains.kotlin.library.UnresolvedLibrary

Expand All @@ -42,6 +43,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration

val infoArgsOnly = configuration.kotlinSourceRoots.isEmpty()
&& configuration[KonanConfigKeys.INCLUDED_LIBRARIES].isNullOrEmpty()
&& configuration[KonanConfigKeys.LIBRARIES_TO_CACHE].isNullOrEmpty()

// TODO: debug info generation mode and debug/release variant selection probably requires some refactoring.
val debug: Boolean get() = configuration.getBoolean(KonanConfigKeys.DEBUG)
Expand Down Expand Up @@ -91,6 +93,9 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
private val includedLibraryFiles
get() = configuration.getList(KonanConfigKeys.INCLUDED_LIBRARIES).map { File(it) }

private val librariesToCacheFiles
get() = configuration.getList(KonanConfigKeys.LIBRARIES_TO_CACHE).map { File(it) }

private val unresolvedLibraries = libraryNames.toUnresolvedLibraries

private val repositories = configuration.getList(KonanConfigKeys.REPOSITORIES)
Expand Down Expand Up @@ -124,8 +129,9 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
// But currently the resolver is in the middle of a complex refactoring so it was decided to avoid changes in its logic.
// TODO: Handle included libraries in KonanLibraryResolver when it's refactored and moved into the big Kotlin repo.
internal val resolvedLibraries by lazy {
val additionalLibraryFiles = includedLibraryFiles + librariesToCacheFiles
resolver.resolveWithDependencies(
unresolvedLibraries + includedLibraryFiles.map { UnresolvedLibrary(it.absolutePath, null) },
unresolvedLibraries + additionalLibraryFiles.map { UnresolvedLibrary(it.absolutePath, null) },
noStdLib = configuration.getBoolean(KonanConfigKeys.NOSTDLIB),
noDefaultLibs = configuration.getBoolean(KonanConfigKeys.NODEFAULTLIBS),
noEndorsedLibs = configuration.getBoolean(KonanConfigKeys.NOENDORSEDLIBS)
Expand All @@ -144,6 +150,16 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
getIncludedLibraries(includedLibraryFiles, configuration, resolvedLibraries)
}

internal val cacheSupport: CacheSupport by lazy {
CacheSupport(configuration, resolvedLibraries, target, produce)
}

internal val cachedLibraries: CachedLibraries
get() = cacheSupport.cachedLibraries

internal val librariesToCache: Set<KotlinLibrary>
get() = cacheSupport.librariesToCache

fun librariesWithDependencies(moduleDescriptor: ModuleDescriptor?): List<KonanLibrary> {
if (moduleDescriptor == null) error("purgeUnneeded() only works correctly after resolve is over, and we have successfully marked package files as needed or not needed.")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create("fully qualified main() name")
val EXPORTED_LIBRARIES: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("libraries included into produced framework API")
val LIBRARIES_TO_CACHE: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("paths to libraries that to be compiled to cache")
val CACHE_DIRECTORIES: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("paths to directories containing caches")
val CACHED_LIBRARIES: CompilerConfigurationKey<Map<String, String>>
= CompilerConfigurationKey.create<Map<String, String>>("mapping from library paths to cache paths")
val FRAMEWORK_IMPORT_HEADERS: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("headers imported to framework header")
val FRIEND_MODULES: CompilerConfigurationKey<List<String>>
Expand Down
Loading

0 comments on commit 8205a26

Please sign in to comment.