[K/N][IR][codegen] Preliminary support of per-file caches

This commit is contained in:
Igor Chevdar
2022-04-04 18:27:15 +05:00
committed by Space
parent 4d2a8f852e
commit 7e79b2b500
35 changed files with 615 additions and 332 deletions
@@ -205,14 +205,6 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
var generateDebugTrampolineString: String? = null var generateDebugTrampolineString: String? = null
@Argument(
value = MAKE_CACHE,
valueDescription = "<path>",
description = "Path of the library to be compiled to cache",
delimiter = ""
)
var librariesToCache: Array<String>? = null
@Argument( @Argument(
value = ADD_CACHE, value = ADD_CACHE,
valueDescription = "<path>", valueDescription = "<path>",
@@ -221,6 +213,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
) )
var libraryToAddToCache: String? = null var libraryToAddToCache: String? = null
@Argument(
value = "-Xfile-to-cache",
valueDescription = "<path>",
description = "Path to file to cache",
delimiter = ""
)
var fileToCache: String? = null
@Argument(value = "-Xexport-kdoc", description = "Export KDoc in framework header") @Argument(value = "-Xexport-kdoc", description = "Export KDoc in framework header")
var exportKDoc: Boolean = false var exportKDoc: Boolean = false
@@ -415,7 +415,6 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
const val STATIC_FRAMEWORK_FLAG = "-Xstatic-framework" const val STATIC_FRAMEWORK_FLAG = "-Xstatic-framework"
const val INCLUDE_ARG = "-Xinclude" const val INCLUDE_ARG = "-Xinclude"
const val CACHED_LIBRARY = "-Xcached-library" const val CACHED_LIBRARY = "-Xcached-library"
const val MAKE_CACHE = "-Xmake-cache"
const val ADD_CACHE = "-Xadd-cache" const val ADD_CACHE = "-Xadd-cache"
const val SHORT_MODULE_NAME_ARG = "-Xshort-module-name" const val SHORT_MODULE_NAME_ARG = "-Xshort-module-name"
} }
@@ -87,9 +87,8 @@ class FakeOverrideBuilder(
fakeOverrideCandidates[clazz] = compatibilityMode fakeOverrideCandidates[clazz] = compatibilityMode
} }
private fun buildFakeOverrideChainsForClass(clazz: IrClass, compatibilityMode: CompatibilityMode) { private fun buildFakeOverrideChainsForClass(clazz: IrClass, compatibilityMode: CompatibilityMode): Boolean {
if (haveFakeOverrides.contains(clazz)) return if (haveFakeOverrides.contains(clazz)) return true
if (!platformSpecificClassFilter.needToConstructFakeOverrides(clazz)) return
val superTypes = clazz.superTypes val superTypes = clazz.superTypes
@@ -99,15 +98,18 @@ class FakeOverrideBuilder(
superClasses.forEach { superClass -> superClasses.forEach { superClass ->
val mode = fakeOverrideCandidates[superClass] ?: compatibilityMode val mode = fakeOverrideCandidates[superClass] ?: compatibilityMode
buildFakeOverrideChainsForClass(superClass, mode) if (buildFakeOverrideChainsForClass(superClass, mode))
haveFakeOverrides.add(superClass) haveFakeOverrides.add(superClass)
} }
if (!platformSpecificClassFilter.needToConstructFakeOverrides(clazz)) return false
fakeOverrideDeclarationTable.run { fakeOverrideDeclarationTable.run {
inFile(clazz.fileOrNull) { inFile(clazz.fileOrNull) {
irOverridingUtil.buildFakeOverridesForClass(clazz, compatibilityMode.oldSignatures) irOverridingUtil.buildFakeOverridesForClass(clazz, compatibilityMode.oldSignatures)
} }
} }
return true
} }
override fun linkFunctionFakeOverride(declaration: IrFakeOverrideFunction, compatibilityMode: Boolean) { override fun linkFunctionFakeOverride(declaration: IrFakeOverrideFunction, compatibilityMode: Boolean) {
@@ -183,7 +183,7 @@ class IrKlibBytesSource(private val klib: IrLibrary, private val fileIndex: Int)
override fun debugInfo(index: Int): ByteArray? = klib.debugInfo(index, fileIndex) override fun debugInfo(index: Int): ByteArray? = klib.debugInfo(index, fileIndex)
} }
internal fun IrLibraryFile.deserializeFqName(fqn: List<Int>): String = fun IrLibraryFile.deserializeFqName(fqn: List<Int>): String =
fqn.joinToString(".", transform = ::string) fqn.joinToString(".", transform = ::string)
fun IrLibraryFile.createFile(module: IrModuleFragment, fileProto: ProtoFile): IrFile { fun IrLibraryFile.createFile(module: IrModuleFragment, fileProto: ProtoFile): IrFile {
@@ -117,7 +117,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
val K2NativeCompilerArguments.isUsefulWithoutFreeArgs: Boolean val K2NativeCompilerArguments.isUsefulWithoutFreeArgs: Boolean
get() = listTargets || listPhases || checkDependencies || !includes.isNullOrEmpty() || get() = listTargets || listPhases || checkDependencies || !includes.isNullOrEmpty() ||
!librariesToCache.isNullOrEmpty() || libraryToAddToCache != null || !exportedLibraries.isNullOrEmpty() libraryToAddToCache != null || !exportedLibraries.isNullOrEmpty()
fun Array<String>?.toNonNullList(): List<String> { fun Array<String>?.toNonNullList(): List<String> {
return this?.asList<String>() ?: listOf<String>() return this?.asList<String>() ?: listOf<String>()
@@ -297,7 +297,6 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
put(OBJC_GENERICS, !arguments.noObjcGenerics) put(OBJC_GENERICS, !arguments.noObjcGenerics)
put(DEBUG_PREFIX_MAP, parseDebugPrefixMap(arguments, configuration)) put(DEBUG_PREFIX_MAP, parseDebugPrefixMap(arguments, configuration))
put(LIBRARIES_TO_CACHE, parseLibrariesToCache(arguments, configuration, outputKind))
val libraryToAddToCache = parseLibraryToAddToCache(arguments, configuration, outputKind) val libraryToAddToCache = parseLibraryToAddToCache(arguments, configuration, outputKind)
if (libraryToAddToCache != null && !arguments.outputName.isNullOrEmpty()) if (libraryToAddToCache != null && !arguments.outputName.isNullOrEmpty())
configuration.report(ERROR, "${K2NativeCompilerArguments.ADD_CACHE} already implicitly sets output file name") configuration.report(ERROR, "${K2NativeCompilerArguments.ADD_CACHE} already implicitly sets output file name")
@@ -305,6 +304,10 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
libraryToAddToCache?.let { put(LIBRARY_TO_ADD_TO_CACHE, it) } libraryToAddToCache?.let { put(LIBRARY_TO_ADD_TO_CACHE, it) }
put(CACHE_DIRECTORIES, cacheDirectories) put(CACHE_DIRECTORIES, cacheDirectories)
put(CACHED_LIBRARIES, parseCachedLibraries(arguments, configuration)) put(CACHED_LIBRARIES, parseCachedLibraries(arguments, configuration))
val fileToCache = arguments.fileToCache
if (outputKind == CompilerOutputKind.PRELIMINARY_CACHE && fileToCache == null)
configuration.report(ERROR, "preliminary_cache only supported for per-file caches")
fileToCache?.let { put(FILE_TO_CACHE, it) }
parseShortModuleName(arguments, configuration, outputKind)?.let { parseShortModuleName(arguments, configuration, outputKind)?.let {
put(SHORT_MODULE_NAME, it) put(SHORT_MODULE_NAME, it)
@@ -521,27 +524,6 @@ private fun parseCachedLibraries(
} }
}.toMap() }.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, "${K2NativeCompilerArguments.MAKE_CACHE} can't be used when not producing cache")
emptyList()
} else if (input.isNotEmpty() && !arguments.libraryToAddToCache.isNullOrEmpty()) {
configuration.report(
ERROR,
"supplied both ${K2NativeCompilerArguments.MAKE_CACHE} and ${K2NativeCompilerArguments.ADD_CACHE} options"
)
emptyList()
} else {
input
}
}
private fun parseLibraryToAddToCache( private fun parseLibraryToAddToCache(
arguments: K2NativeCompilerArguments, arguments: K2NativeCompilerArguments,
configuration: CompilerConfiguration, configuration: CompilerConfiguration,
@@ -43,12 +43,6 @@ const val INCLUDE_ARG = MovedK2NativeCompilerArguments.INCLUDE_ARG
) )
const val CACHED_LIBRARY = MovedK2NativeCompilerArguments.CACHED_LIBRARY const val CACHED_LIBRARY = MovedK2NativeCompilerArguments.CACHED_LIBRARY
@Deprecated(
"Moved to new 'org.jetbrains.kotlin.cli.common.arguments' package",
ReplaceWith("MAKE_CACHE", "org.jetbrains.kotlin.cli.common.arguments.MAKE_CACHE")
)
const val MAKE_CACHE = MovedK2NativeCompilerArguments.MAKE_CACHE
@Deprecated( @Deprecated(
"Moved to new 'org.jetbrains.kotlin.cli.common.arguments' package", "Moved to new 'org.jetbrains.kotlin.cli.common.arguments' package",
ReplaceWith("ADD_CACHE", "org.jetbrains.kotlin.cli.common.arguments.ADD_CACHE") ReplaceWith("ADD_CACHE", "org.jetbrains.kotlin.cli.common.arguments.ADD_CACHE")
@@ -173,7 +173,7 @@ internal fun initializeCachedBoxes(context: Context) {
val rangeStart = "${cache.name}_RANGE_FROM" val rangeStart = "${cache.name}_RANGE_FROM"
val rangeEnd = "${cache.name}_RANGE_TO" val rangeEnd = "${cache.name}_RANGE_TO"
initCache(cache, context, cacheName, rangeStart, rangeEnd, initCache(cache, context, cacheName, rangeStart, rangeEnd,
declareOnly = !context.producedLlvmModuleContainsStdlib declareOnly = !context.shouldDefineCachedBoxes
).also { context.llvm.boxCacheGlobals[cache] = it } ).also { context.llvm.boxCacheGlobals[cache] = it }
} }
} }
@@ -5,6 +5,10 @@
package org.jetbrains.kotlin.backend.konan package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.common.serialization.IrKlibBytesSource
import org.jetbrains.kotlin.backend.common.serialization.IrLibraryFileFromBytes
import org.jetbrains.kotlin.backend.common.serialization.codedInputStream
import org.jetbrains.kotlin.backend.common.serialization.deserializeFqName
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.file.File
@@ -12,16 +16,31 @@ import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.protobuf.ExtensionRegistryLite
import org.jetbrains.kotlin.backend.common.serialization.proto.IrFile as ProtoFile
sealed class CacheDeserializationStrategy { sealed class CacheDeserializationStrategy {
abstract fun contains(filePath: String): Boolean abstract fun contains(filePath: String): Boolean
abstract fun contains(fqName: FqName, fileName: String): Boolean
object Nothing : CacheDeserializationStrategy() { object Nothing : CacheDeserializationStrategy() {
override fun contains(filePath: String) = false override fun contains(filePath: String) = false
override fun contains(fqName: FqName, fileName: String) = false
} }
object WholeModule : CacheDeserializationStrategy() { object WholeModule : CacheDeserializationStrategy() {
override fun contains(filePath: String) = true override fun contains(filePath: String) = true
override fun contains(fqName: FqName, fileName: String) = true
}
class SingleFile(val filePath: String) : CacheDeserializationStrategy() {
override fun contains(filePath: String) = filePath == this.filePath
lateinit var fqName: String
override fun contains(fqName: FqName, fileName: String) =
fqName.asString() == this.fqName && File(filePath).name == fileName
} }
} }
@@ -32,7 +51,7 @@ class CacheSupport(
resolvedLibraries: KotlinLibraryResolveResult, resolvedLibraries: KotlinLibraryResolveResult,
ignoreCacheReason: String?, ignoreCacheReason: String?,
target: KonanTarget, target: KonanTarget,
produce: CompilerOutputKind val produce: CompilerOutputKind
) { ) {
private val allLibraries = resolvedLibraries.getFullList() private val allLibraries = resolvedLibraries.getFullList()
@@ -46,14 +65,34 @@ class CacheSupport(
} }
internal fun tryGetImplicitOutput(): String? { internal fun tryGetImplicitOutput(): String? {
val libraryToAddToCache = configuration.get(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE) ?: return null val libraryToCache = libraryToCache ?: return null
// Put the resulting library in the first cache directory. // Put the resulting library in the first cache directory.
val cacheDirectory = implicitCacheDirectories.firstOrNull() ?: return null val cacheDirectory = implicitCacheDirectories.firstOrNull() ?: return null
val libraryToAddToCacheFile = File(libraryToAddToCache) val singleFileStrategy = libraryToCache.strategy as? CacheDeserializationStrategy.SingleFile
val library = allLibraries.single { it.libraryFile == libraryToAddToCacheFile } val baseLibraryCacheDirectory = cacheDirectory.child(
return cacheDirectory.child(CachedLibraries.getCachedLibraryName(library)).absolutePath if (singleFileStrategy == null)
} CachedLibraries.getCachedLibraryName(libraryToCache.klib)
else
CachedLibraries.getPerFileCachedLibraryName(libraryToCache.klib)
)
if (singleFileStrategy == null)
return baseLibraryCacheDirectory.absolutePath
val fileToCache = singleFileStrategy.filePath
val fileProtos = Array<ProtoFile>(libraryToCache.klib.fileCount()) {
ProtoFile.parseFrom(libraryToCache.klib.file(it).codedInputStream, ExtensionRegistryLite.newInstance())
}
val fileIndex = fileProtos.indexOfFirst { it.fileEntry.name == fileToCache }
require(fileIndex >= 0) { "No file found in klib with path $fileToCache" }
val fileReader = IrLibraryFileFromBytes(IrKlibBytesSource(libraryToCache.klib, fileIndex))
val fqName = fileReader.deserializeFqName(fileProtos[fileIndex].fqNameList)
singleFileStrategy.fqName = fqName
val fileCacheDirectory = baseLibraryCacheDirectory.child(cacheFileId(fqName, fileToCache))
val contentDirName = if (produce == CompilerOutputKind.PRELIMINARY_CACHE)
CachedLibraries.PER_FILE_CACHE_IR_LEVEL_DIR_NAME
else CachedLibraries.PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME
return fileCacheDirectory.child(contentDirName).absolutePath
}
internal val cachedLibraries: CachedLibraries = run { internal val cachedLibraries: CachedLibraries = run {
val explicitCacheFiles = configuration.get(KonanConfigKeys.CACHED_LIBRARIES)!! val explicitCacheFiles = configuration.get(KonanConfigKeys.CACHED_LIBRARIES)!!
@@ -86,39 +125,33 @@ class CacheSupport(
"not found among resolved libraries:\n " + "not found among resolved libraries:\n " +
allLibraries.joinToString("\n ") { it.libraryFile.absolutePath }) allLibraries.joinToString("\n ") { it.libraryFile.absolutePath })
internal val librariesToCache: Set<KotlinLibrary> = run { internal val libraryToCache = configuration.get(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE)?.let {
val libraryToAddToCachePath = configuration.get(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE) val libraryToAddToCacheFile = File(it)
if (libraryToAddToCachePath.isNullOrEmpty()) { val libraryToAddToCache = getLibrary(libraryToAddToCacheFile)
configuration.get(KonanConfigKeys.LIBRARIES_TO_CACHE)!! val libraryCache = cachedLibraries.getLibraryCache(libraryToAddToCache)
.map { getLibrary(File(it)) } if (libraryCache != null && libraryCache.granularity == CachedLibraries.Granularity.MODULE)
.toSet() null
.also { if (!produce.isCache) check(it.isEmpty()) } else {
} else { val fileToCache = configuration.get(KonanConfigKeys.FILE_TO_CACHE)
val libraryToAddToCacheFile = File(libraryToAddToCachePath) PartialCacheInfo(libraryToAddToCache, if (fileToCache == null) CacheDeserializationStrategy.WholeModule else CacheDeserializationStrategy.SingleFile(fileToCache))
val libraryToAddToCache = getLibrary(libraryToAddToCacheFile)
val libraryCache = cachedLibraries.getLibraryCache(libraryToAddToCache)
if (libraryCache == null)
setOf(libraryToAddToCache)
else
emptySet()
} }
} }
internal val libraryToCache = librariesToCache.singleOrNull()?.let {
PartialCacheInfo(it, CacheDeserializationStrategy.WholeModule)
}
internal val preLinkCaches: Boolean = internal val preLinkCaches: Boolean =
configuration.get(KonanConfigKeys.PRE_LINK_CACHES, false) 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)}"
}
init { init {
// Ensure dependencies of every cached library are cached too: // Ensure dependencies of every cached library are cached too:
resolvedLibraries.getFullList { libraries -> resolvedLibraries.getFullList { libraries ->
libraries.map { library -> libraries.map { library ->
val cache = cachedLibraries.getLibraryCache(library.library) val cache = cachedLibraries.getLibraryCache(library.library)
if (cache != null || library.library in librariesToCache) { if (cache != null || library.library == libraryToCache?.klib) {
library.resolvedDependencies.forEach { library.resolvedDependencies.forEach {
if (!cachedLibraries.isLibraryCached(it.library) && it.library !in librariesToCache) { if (!cachedLibraries.isLibraryCached(it.library) && it.library != libraryToCache?.klib) {
val description = if (cache != null) { val description = if (cache != null) {
"cached (in ${cache.path})" "cached (in ${cache.path})"
} else { } else {
@@ -137,15 +170,15 @@ class CacheSupport(
} }
// Ensure not making cache for libraries that are already cached: // Ensure not making cache for libraries that are already cached:
librariesToCache.forEach { libraryToCache?.klib?.let {
val cache = cachedLibraries.getLibraryCache(it) val cache = cachedLibraries.getLibraryCache(it)
if (cache != null) { if (cache != null && cache.granularity == CachedLibraries.Granularity.MODULE) {
configuration.reportCompilationError("Can't cache library '${it.libraryName}' " + configuration.reportCompilationError("can't cache library '${it.libraryName}' " +
"that is already cached in '${cache.path}'") "that is already cached in '${cache.path}'")
} }
} }
if ((librariesToCache.isNotEmpty() || cachedLibraries.hasDynamicCaches || cachedLibraries.hasStaticCaches) if ((libraryToCache != null || cachedLibraries.hasDynamicCaches || cachedLibraries.hasStaticCaches)
&& configuration.getBoolean(KonanConfigKeys.OPTIMIZATION)) { && configuration.getBoolean(KonanConfigKeys.OPTIMIZATION)) {
configuration.reportCompilationError("Cache cannot be used in optimized compilation") configuration.reportCompilationError("Cache cannot be used in optimized compilation")
} }
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.serialization.ClassFieldsSerializer import org.jetbrains.kotlin.backend.konan.serialization.ClassFieldsSerializer
import org.jetbrains.kotlin.backend.konan.serialization.InlineFunctionBodyReferenceSerializer import org.jetbrains.kotlin.backend.konan.serialization.InlineFunctionBodyReferenceSerializer
import org.jetbrains.kotlin.backend.konan.serialization.SerializedClassFields
import org.jetbrains.kotlin.backend.konan.serialization.SerializedInlineFunctionReference
import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -19,25 +21,69 @@ class CachedLibraries(
explicitCaches: Map<KotlinLibrary, String>, explicitCaches: Map<KotlinLibrary, String>,
implicitCacheDirectories: List<File> implicitCacheDirectories: List<File>
) { ) {
enum class Kind { DYNAMIC, STATIC }
enum class Granularity { MODULE, FILE }
class Cache(val kind: Kind, val path: String) { private fun Kind.toCompilerOutputKind(): CompilerOutputKind = when (this) {
enum class Kind { DYNAMIC, STATIC } Kind.DYNAMIC -> CompilerOutputKind.DYNAMIC_CACHE
Kind.STATIC -> CompilerOutputKind.STATIC_CACHE
}
inner class Cache(val kind: Kind, val granularity: Granularity, val path: String) {
val fileDirs by lazy { File(path).listFiles.filter { it.isDirectory }.sortedBy { it.name } }
val bitcodeDependencies by lazy { val bitcodeDependencies by lazy {
val directory = File(path).absoluteFile.parent when (granularity) {
File(directory, BITCODE_DEPENDENCIES_FILE_NAME).readStrings() Granularity.MODULE -> File(path).parentFile.child(BITCODE_DEPENDENCIES_FILE_NAME).readStrings()
Granularity.FILE -> fileDirs.flatMap {
it.child(PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME).child(BITCODE_DEPENDENCIES_FILE_NAME).readStrings()
}.distinct()
}
}
val binariesPaths by lazy {
when (granularity) {
Granularity.MODULE -> listOf(path)
Granularity.FILE -> fileDirs.map {
it.child(PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME).child(getArtifactName(it.name, kind.toCompilerOutputKind())).absolutePath
}
}
} }
val serializedInlineFunctionBodies by lazy { val serializedInlineFunctionBodies by lazy {
val directory = File(path).absoluteFile.parent val result = mutableListOf<SerializedInlineFunctionReference>()
val data = File(directory, INLINE_FUNCTION_BODIES_FILE_NAME).readBytes() when (granularity) {
InlineFunctionBodyReferenceSerializer.deserialize(data) Granularity.MODULE -> {
val directory = File(path).absoluteFile.parent
val data = File(directory, INLINE_FUNCTION_BODIES_FILE_NAME).readBytes()
InlineFunctionBodyReferenceSerializer.deserializeTo(data, result)
}
Granularity.FILE -> {
fileDirs.forEach { fileDir ->
val data = fileDir.child(PER_FILE_CACHE_IR_LEVEL_DIR_NAME).child(INLINE_FUNCTION_BODIES_FILE_NAME).readBytes()
InlineFunctionBodyReferenceSerializer.deserializeTo(data, result)
}
}
}
result
} }
val serializedClassFields by lazy { val serializedClassFields by lazy {
val directory = File(path).absoluteFile.parent val result = mutableListOf<SerializedClassFields>()
val data = File(directory, CLASS_FIELDS_FILE_NAME).readBytes() when (granularity) {
ClassFieldsSerializer.deserialize(data) Granularity.MODULE -> {
val directory = File(path).absoluteFile.parent
val data = File(directory, CLASS_FIELDS_FILE_NAME).readBytes()
ClassFieldsSerializer.deserializeTo(data, result)
}
Granularity.FILE -> {
fileDirs.forEach { fileDir ->
val data = fileDir.child(PER_FILE_CACHE_IR_LEVEL_DIR_NAME).child(CLASS_FIELDS_FILE_NAME).readBytes()
ClassFieldsSerializer.deserializeTo(data, result)
}
}
}
result
} }
} }
@@ -57,9 +103,9 @@ class CachedLibraries(
error("Both dynamic and static caches files cannot be in the same directory." + error("Both dynamic and static caches files cannot be in the same directory." +
" Library: ${library.libraryName}, path to cache: ${cacheDir.absolutePath}") " Library: ${library.libraryName}, path to cache: ${cacheDir.absolutePath}")
return when { return when {
dynamicFile.absolutePath in cacheDirContents -> Cache(Cache.Kind.DYNAMIC, dynamicFile.absolutePath) dynamicFile.absolutePath in cacheDirContents -> Cache(Kind.DYNAMIC, Granularity.MODULE, dynamicFile.absolutePath)
staticFile.absolutePath in cacheDirContents -> Cache(Cache.Kind.STATIC, staticFile.absolutePath) staticFile.absolutePath in cacheDirContents -> Cache(Kind.STATIC, Granularity.MODULE, staticFile.absolutePath)
else -> error("No cache found for library ${library.libraryName} at ${cacheDir.absolutePath}") else -> Cache(Kind.STATIC, Granularity.FILE, cacheDir.absolutePath)
} }
} }
@@ -71,7 +117,8 @@ class CachedLibraries(
?: error("No cache found for library ${library.libraryName} at $explicitPath") ?: error("No cache found for library ${library.libraryName} at $explicitPath")
} else { } else {
implicitCacheDirectories.firstNotNullOfOrNull { dir -> implicitCacheDirectories.firstNotNullOfOrNull { dir ->
selectCache(library, dir.child(getCachedLibraryName(library))) selectCache(library, dir.child(getPerFileCachedLibraryName(library)))
?: selectCache(library, dir.child(getCachedLibraryName(library)))
} }
} }
@@ -89,22 +136,26 @@ class CachedLibraries(
val hasStaticCaches = allCaches.values.any { val hasStaticCaches = allCaches.values.any {
when (it.kind) { when (it.kind) {
Cache.Kind.STATIC -> true Kind.STATIC -> true
Cache.Kind.DYNAMIC -> false Kind.DYNAMIC -> false
} }
} }
val hasDynamicCaches = allCaches.values.any { val hasDynamicCaches = allCaches.values.any {
when (it.kind) { when (it.kind) {
Cache.Kind.STATIC -> false Kind.STATIC -> false
Cache.Kind.DYNAMIC -> true Kind.DYNAMIC -> true
} }
} }
companion object { companion object {
fun getPerFileCachedLibraryName(library: KotlinLibrary): String = "${library.uniqueName}-per-file-cache"
fun getCachedLibraryName(library: KotlinLibrary): String = getCachedLibraryName(library.uniqueName) fun getCachedLibraryName(library: KotlinLibrary): String = getCachedLibraryName(library.uniqueName)
fun getCachedLibraryName(libraryName: String): String = "$libraryName-cache" fun getCachedLibraryName(libraryName: String): String = "$libraryName-cache"
const val PER_FILE_CACHE_IR_LEVEL_DIR_NAME = "ir"
const val PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME = "bin"
const val BITCODE_DEPENDENCIES_FILE_NAME = "bitcode_deps" const val BITCODE_DEPENDENCIES_FILE_NAME = "bitcode_deps"
const val INLINE_FUNCTION_BODIES_FILE_NAME = "inline_bodies" const val INLINE_FUNCTION_BODIES_FILE_NAME = "inline_bodies"
const val CLASS_FIELDS_FILE_NAME = "class_fields" const val CLASS_FIELDS_FILE_NAME = "class_fields"
@@ -31,10 +31,9 @@ import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
val KonanConfig.isFinalBinary: Boolean get() = when (this.produce) { val KonanConfig.isFinalBinary: Boolean get() = when (this.produce) {
CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC, CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC,
CompilerOutputKind.STATIC -> true CompilerOutputKind.STATIC -> true
CompilerOutputKind.DYNAMIC_CACHE, CompilerOutputKind.STATIC_CACHE, CompilerOutputKind.DYNAMIC_CACHE, CompilerOutputKind.STATIC_CACHE, CompilerOutputKind.PRELIMINARY_CACHE,
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
CompilerOutputKind.FRAMEWORK -> !omitFrameworkBinary CompilerOutputKind.FRAMEWORK -> !omitFrameworkBinary
else -> error("Unknown compiler output kind")
} }
val CompilerOutputKind.involvesBitcodeGeneration: Boolean val CompilerOutputKind.involvesBitcodeGeneration: Boolean
@@ -43,18 +42,30 @@ val CompilerOutputKind.involvesBitcodeGeneration: Boolean
internal val Context.producedLlvmModuleContainsStdlib: Boolean internal val Context.producedLlvmModuleContainsStdlib: Boolean
get() = this.llvmModuleSpecification.containsModule(this.stdlibModule) get() = this.llvmModuleSpecification.containsModule(this.stdlibModule)
internal val Context.shouldDefineFunctionClasses: Boolean
get() = producedLlvmModuleContainsStdlib &&
config.libraryToCache?.strategy?.contains(KonanFqNames.internalPackageName, "KFunctionImpl.kt") != false
internal val Context.shouldDefineCachedBoxes: Boolean
get() = producedLlvmModuleContainsStdlib &&
config.libraryToCache?.strategy?.contains(KonanFqNames.internalPackageName, "Boxing.kt") != false
internal val Context.shouldLinkRuntimeNativeLibraries: Boolean
get() = producedLlvmModuleContainsStdlib &&
config.libraryToCache?.strategy?.contains(KonanFqNames.packageName, "Runtime.kt") != false
val KonanConfig.involvesLinkStage: Boolean val KonanConfig.involvesLinkStage: Boolean
get() = when (this.produce) { get() = when (this.produce) {
CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC, CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC,
CompilerOutputKind.DYNAMIC_CACHE, CompilerOutputKind.STATIC_CACHE, CompilerOutputKind.DYNAMIC_CACHE, CompilerOutputKind.STATIC_CACHE,
CompilerOutputKind.STATIC-> true CompilerOutputKind.STATIC -> true
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE, CompilerOutputKind.PRELIMINARY_CACHE -> false
CompilerOutputKind.FRAMEWORK -> !omitFrameworkBinary CompilerOutputKind.FRAMEWORK -> !omitFrameworkBinary
else -> error("Unknown compiler output kind")
} }
val CompilerOutputKind.isCache: Boolean val CompilerOutputKind.isCache: Boolean
get() = (this == CompilerOutputKind.STATIC_CACHE || this == CompilerOutputKind.DYNAMIC_CACHE) get() = this == CompilerOutputKind.STATIC_CACHE || this == CompilerOutputKind.DYNAMIC_CACHE
|| this == CompilerOutputKind.PRELIMINARY_CACHE
internal fun llvmIrDumpCallback(state: ActionState, module: IrModuleFragment, context: Context) { internal fun llvmIrDumpCallback(state: ActionState, module: IrModuleFragment, context: Context) {
module.let{} module.let{}
@@ -117,7 +128,6 @@ private fun collectLlvmModules(context: Context, generatedBitcodeFiles: List<Str
exceptionsSupportNativeLibrary exceptionsSupportNativeLibrary
val runtimeNativeLibraries = context.config.runtimeNativeLibraries val runtimeNativeLibraries = context.config.runtimeNativeLibraries
.takeIf { context.producedLlvmModuleContainsStdlib }.orEmpty()
fun parseBitcodeFiles(files: List<String>): List<LLVMModuleRef> = files.map { bitcodeFile -> fun parseBitcodeFiles(files: List<String>): List<LLVMModuleRef> = files.map { bitcodeFile ->
@@ -128,7 +138,10 @@ private fun collectLlvmModules(context: Context, generatedBitcodeFiles: List<Str
parsedModule parsedModule
} }
val runtimeModules = parseBitcodeFiles(runtimeNativeLibraries + bitcodePartOfStdlib) val runtimeModules = parseBitcodeFiles(
(runtimeNativeLibraries + bitcodePartOfStdlib)
.takeIf { context.shouldLinkRuntimeNativeLibraries }.orEmpty()
)
val additionalModules = parseBitcodeFiles(additionalBitcodeFiles) val additionalModules = parseBitcodeFiles(additionalBitcodeFiles)
return LlvmModules( return LlvmModules(
runtimeModules.ifNotEmpty { this + context.generateRuntimeConstantsModule() } ?: emptyList(), runtimeModules.ifNotEmpty { this + context.generateRuntimeConstantsModule() } ?: emptyList(),
@@ -178,13 +191,14 @@ internal fun linkBitcodeDependencies(context: Context) {
embedAppleLinkerOptionsToBitcode(context.llvm, context.config) embedAppleLinkerOptionsToBitcode(context.llvm, context.config)
} }
linkAllDependencies(context, generatedBitcodeFiles) linkAllDependencies(context, generatedBitcodeFiles)
} }
internal fun produceOutput(context: Context) { internal fun produceOutput(context: Context) {
val config = context.config.configuration val config = context.config.configuration
val tempFiles = context.config.tempFiles val tempFiles = context.config.tempFiles
val produce = config.get(KonanConfigKeys.PRODUCE) val produce = context.config.produce
if (produce == CompilerOutputKind.FRAMEWORK) { if (produce == CompilerOutputKind.FRAMEWORK) {
context.objCExport.produceFrameworkInterface() context.objCExport.produceFrameworkInterface()
if (context.config.omitFrameworkBinary) { if (context.config.omitFrameworkBinary) {
@@ -256,8 +270,7 @@ internal fun produceOutput(context: Context) {
context.bitcodeFileName = output context.bitcodeFileName = output
LLVMWriteBitcodeToFile(context.llvmModule!!, output) LLVMWriteBitcodeToFile(context.llvmModule!!, output)
} }
null -> {} CompilerOutputKind.PRELIMINARY_CACHE -> {}
else -> error("Unknown compiler output kind")
} }
} }
@@ -496,7 +496,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
val llvmModuleSpecification: LlvmModuleSpecification by lazy { val llvmModuleSpecification: LlvmModuleSpecification by lazy {
when { when {
config.produce.isCache -> config.produce.isCache ->
CacheLlvmModuleSpecification(config.cachedLibraries, config.librariesToCache) CacheLlvmModuleSpecification(config.cachedLibraries, config.libraryToCache!!.klib)
else -> DefaultLlvmModuleSpecification(config.cachedLibraries) else -> DefaultLlvmModuleSpecification(config.cachedLibraries)
} }
} }
@@ -514,6 +514,9 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
val classFields = mutableListOf<SerializedClassFields>() val classFields = mutableListOf<SerializedClassFields>()
val calledFromExportedInlineFunctions = mutableSetOf<IrFunction>()
val constructedFromExportedInlineFunctions = mutableSetOf<IrClass>()
val targetAbiInfo: TargetAbiInfo by lazy { val targetAbiInfo: TargetAbiInfo by lazy {
when { when {
config.target == KonanTarget.MINGW_X64 -> { config.target == KonanTarget.MINGW_X64 -> {
@@ -7,14 +7,17 @@ package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.llvm.llvmSymbolOrigin import org.jetbrains.kotlin.backend.konan.llvm.llvmSymbolOrigin
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrModuleFragmentImpl import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
import org.jetbrains.kotlin.ir.util.NaiveSourceBasedFileEntryImpl import org.jetbrains.kotlin.ir.util.NaiveSourceBasedFileEntryImpl
import org.jetbrains.kotlin.ir.util.addChild import org.jetbrains.kotlin.ir.util.addChild
import org.jetbrains.kotlin.ir.util.addFile import org.jetbrains.kotlin.ir.util.addFile
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.MemberScope
/** /**
* Sometimes we need to reference symbols that are not declared in metadata. * Sometimes we need to reference symbols that are not declared in metadata.
@@ -38,14 +41,21 @@ internal class InternalAbi(private val context: Context) {
/** /**
* Representation of ABI files from external modules. * Representation of ABI files from external modules.
*/ */
private val externalAbiFiles = mutableMapOf<ModuleDescriptor, IrFile>() private val externalAbiFiles = mutableMapOf<ModuleDescriptor, IrExternalPackageFragment>()
fun init(modules: List<IrModuleFragment>) { fun init(modules: List<IrModuleFragment>) {
internalAbiFiles = modules.associate { it.descriptor to createAbiFile(it) } internalAbiFiles = modules.associate { it.descriptor to createAbiFile(it) }
} }
private fun createAbiFile(module: IrModuleFragment): IrFile = private fun createAbiFile(module: IrModuleFragment): IrFile =
module.addFile(NaiveSourceBasedFileEntryImpl("internal"), FqName("kotlin.native.caches.abi")) module.addFile(NaiveSourceBasedFileEntryImpl("internal"), KonanFqNames.cachesInternalAbi)
private fun createExternalAbiFile(module: ModuleDescriptor, fqName: FqName): IrExternalPackageFragment {
val packageFragmentDescriptor = object : PackageFragmentDescriptorImpl(module, fqName) {
override fun getMemberScope(): MemberScope = MemberScope.Empty
}
return IrExternalPackageFragmentImpl(IrExternalPackageFragmentSymbolImpl(packageFragmentDescriptor), fqName)
}
/** /**
* Adds external [function] from [module] to a list of external references. * Adds external [function] from [module] to a list of external references.
@@ -53,9 +63,8 @@ internal class InternalAbi(private val context: Context) {
fun reference(function: IrFunction, module: ModuleDescriptor) { fun reference(function: IrFunction, module: ModuleDescriptor) {
assert(function.isExternal) { "Function that represents external ABI should be marked as external" } assert(function.isExternal) { "Function that represents external ABI should be marked as external" }
context.llvmImports.add(module.llvmSymbolOrigin) context.llvmImports.add(module.llvmSymbolOrigin)
externalAbiFiles.getOrPut(module) { val externalAbiFile = externalAbiFiles.getOrPut(module) { createExternalAbiFile(module, KonanFqNames.cachesInternalAbi) }
createAbiFile(IrModuleFragmentImpl(module, context.irBuiltIns)) externalAbiFile.addChild(function)
}.addChild(function)
} }
/** /**
@@ -91,6 +91,14 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val objCObjectBase = packageScope.getContributedClass("ObjCObjectBase") val objCObjectBase = packageScope.getContributedClass("ObjCObjectBase")
val objCObjectBaseMeta = packageScope.getContributedClass("ObjCObjectBaseMeta")
val objCClass = packageScope.getContributedClass("ObjCClass")
val objCClassOf = packageScope.getContributedClass("ObjCClassOf")
val objCProtocol = packageScope.getContributedClass("ObjCProtocol")
val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single() val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single()
val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single() val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single()
@@ -394,27 +394,27 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
internal val cachedLibraries: CachedLibraries internal val cachedLibraries: CachedLibraries
get() = cacheSupport.cachedLibraries get() = cacheSupport.cachedLibraries
internal val librariesToCache: Set<KotlinLibrary>
get() = cacheSupport.librariesToCache
internal val libraryToCache: PartialCacheInfo? internal val libraryToCache: PartialCacheInfo?
get() = cacheSupport.libraryToCache get() = cacheSupport.libraryToCache
internal val producePerFileCache = libraryToCache?.strategy is CacheDeserializationStrategy.SingleFile
val outputFiles = val outputFiles =
OutputFiles(configuration.get(KonanConfigKeys.OUTPUT) ?: cacheSupport.tryGetImplicitOutput(), OutputFiles(configuration.get(KonanConfigKeys.OUTPUT) ?: cacheSupport.tryGetImplicitOutput(),
target, produce) target, produce, producePerFileCache)
val tempFiles = TempFiles(outputFiles.outputName, configuration.get(KonanConfigKeys.TEMPORARY_FILES_DIR)) val tempFiles = TempFiles(outputFiles.outputName, configuration.get(KonanConfigKeys.TEMPORARY_FILES_DIR))
val outputFile get() = outputFiles.mainFile val outputFile get() = outputFiles.mainFileName
private val implicitModuleName: String private val implicitModuleName: String
get() = File(outputFiles.outputName).name get() = if (produce.isCache) outputFiles.cacheFileName else File(outputFiles.outputName).name
val infoArgsOnly = configuration.kotlinSourceRoots.isEmpty() val infoArgsOnly = (configuration.kotlinSourceRoots.isEmpty()
&& configuration[KonanConfigKeys.INCLUDED_LIBRARIES].isNullOrEmpty() && configuration[KonanConfigKeys.INCLUDED_LIBRARIES].isNullOrEmpty()
&& librariesToCache.isEmpty()
&& configuration[KonanConfigKeys.EXPORTED_LIBRARIES].isNullOrEmpty() && configuration[KonanConfigKeys.EXPORTED_LIBRARIES].isNullOrEmpty()
&& libraryToCache == null)
|| (producePerFileCache && outputFiles.mainFile.exists)
/** /**
* Do not compile binary when compiling framework. * Do not compile binary when compiling framework.
@@ -44,6 +44,8 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create<List<String>>("paths to directories containing caches") = CompilerConfigurationKey.create<List<String>>("paths to directories containing caches")
val CACHED_LIBRARIES: CompilerConfigurationKey<Map<String, String>> val CACHED_LIBRARIES: CompilerConfigurationKey<Map<String, String>>
= CompilerConfigurationKey.create<Map<String, String>>("mapping from library paths to cache paths") = CompilerConfigurationKey.create<Map<String, String>>("mapping from library paths to cache paths")
val FILE_TO_CACHE: CompilerConfigurationKey<String?>
= CompilerConfigurationKey.create<String?>("which file should be compiled to cache")
val FRAMEWORK_IMPORT_HEADERS: CompilerConfigurationKey<List<String>> val FRAMEWORK_IMPORT_HEADERS: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("headers imported to framework header") = CompilerConfigurationKey.create<List<String>>("headers imported to framework header")
val FRIEND_MODULES: CompilerConfigurationKey<List<String>> val FRIEND_MODULES: CompilerConfigurationKey<List<String>>
@@ -39,4 +39,5 @@ object KonanFqNames {
val noReorderFields = FqName("kotlin.native.internal.NoReorderFields") val noReorderFields = FqName("kotlin.native.internal.NoReorderFields")
val objCName = FqName("kotlin.native.ObjCName") val objCName = FqName("kotlin.native.ObjCName")
val reflectionPackageName = FqName("kotlin.native.internal.ReflectionPackageName") val reflectionPackageName = FqName("kotlin.native.internal.ReflectionPackageName")
val cachesInternalAbi = FqName("kotlin.native.caches.abi")
} }
@@ -26,11 +26,8 @@ class KonanLibrariesResolveSupport(
private val includedLibraryFiles = private val includedLibraryFiles =
configuration.getList(KonanConfigKeys.INCLUDED_LIBRARIES).map { File(it) } configuration.getList(KonanConfigKeys.INCLUDED_LIBRARIES).map { File(it) }
private val librariesToCacheFiles = private val libraryToCacheFile =
configuration.getList(KonanConfigKeys.LIBRARIES_TO_CACHE).map { File(it) } + configuration.get(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE)?.let { File(it) }
configuration.get(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE).let {
if (it.isNullOrEmpty()) emptyList() else listOf(File(it))
}
private val libraryNames = configuration.getList(KonanConfigKeys.LIBRARY_FILES) private val libraryNames = configuration.getList(KonanConfigKeys.LIBRARY_FILES)
@@ -62,7 +59,7 @@ class KonanLibrariesResolveSupport(
// But currently the resolver is in the middle of a complex refactoring so it was decided to avoid changes in its logic. // 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. // TODO: Handle included libraries in KonanLibraryResolver when it's refactored and moved into the big Kotlin repo.
internal val resolvedLibraries = run { internal val resolvedLibraries = run {
val additionalLibraryFiles = includedLibraryFiles + librariesToCacheFiles val additionalLibraryFiles = includedLibraryFiles + listOfNotNull(libraryToCacheFile)
resolver.resolveWithDependencies( resolver.resolveWithDependencies(
unresolvedLibraries + additionalLibraryFiles.map { UnresolvedLibrary(it.absolutePath, null) }, unresolvedLibraries + additionalLibraryFiles.map { UnresolvedLibrary(it.absolutePath, null) },
noStdLib = configuration.getBoolean(KonanConfigKeys.NOSTDLIB), noStdLib = configuration.getBoolean(KonanConfigKeys.NOSTDLIB),
@@ -34,6 +34,50 @@ internal fun determineLinkerOutput(context: Context): LinkerOutputKind =
else -> TODO("${context.config.produce} should not reach native linker stage") else -> TODO("${context.config.produce} should not reach native linker stage")
} }
internal class CacheStorage(val context: Context) {
private val isPreliminaryCache = context.config.produce == CompilerOutputKind.PRELIMINARY_CACHE
fun renameOutput() {
val outputFiles = context.config.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.
outputFiles.mainFile.delete()
if (!outputFiles.tempCacheDirectory!!.renameTo(outputFiles.mainFile))
outputFiles.tempCacheDirectory.deleteRecursively()
}
fun saveAdditionalCacheInfo() {
context.config.outputFiles.tempCacheDirectory!!.mkdirs()
if (!isPreliminaryCache)
saveCacheBitcodeDependencies()
if (isPreliminaryCache || context.configuration.get(KonanConfigKeys.FILE_TO_CACHE) == null) {
saveInlineFunctionBodies()
saveClassFields()
}
}
private fun saveCacheBitcodeDependencies() {
val bitcodeDependencies = context.config.resolvedLibraries
.getFullList(TopologicalLibraryOrder)
.filter {
require(it is KonanLibrary)
context.llvmImports.bitcodeIsUsed(it)
&& it != context.config.cacheSupport.libraryToCache?.klib // Skip loops.
}.cast<List<KonanLibrary>>()
context.config.outputFiles.bitcodeDependenciesFile!!.writeLines(bitcodeDependencies.map { it.uniqueName })
}
private fun saveInlineFunctionBodies() {
context.config.outputFiles.inlineFunctionBodiesFile!!.writeBytes(
InlineFunctionBodyReferenceSerializer.serialize(context.inlineFunctionBodies))
}
private fun saveClassFields() {
context.config.outputFiles.classFieldsFile!!.writeBytes(
ClassFieldsSerializer.serialize(context.classFields))
}
}
// TODO: We have a Linker.kt file in the shared module. // TODO: We have a Linker.kt file in the shared module.
internal class Linker(val context: Context) { internal class Linker(val context: Context) {
@@ -48,65 +92,13 @@ internal class Linker(val context: Context) {
fun link(objectFiles: List<ObjectFile>) { fun link(objectFiles: List<ObjectFile>) {
val nativeDependencies = context.llvm.nativeDependenciesToLink val nativeDependencies = context.llvm.nativeDependenciesToLink
val includedBinariesLibraries = if (context.config.produce.isCache) { val includedBinariesLibraries = context.config.libraryToCache?.let { listOf(it.klib) }
context.config.librariesToCache ?: nativeDependencies.filterNot { context.config.cachedLibraries.isLibraryCached(it) }
} else {
nativeDependencies.filterNot { context.config.cachedLibraries.isLibraryCached(it) }
}
val includedBinaries = includedBinariesLibraries.map { (it as? KonanLibrary)?.includedPaths.orEmpty() }.flatten() val includedBinaries = includedBinariesLibraries.map { (it as? KonanLibrary)?.includedPaths.orEmpty() }.flatten()
val libraryProvidedLinkerFlags = context.llvm.allNativeDependencies.map { it.linkerOpts }.flatten() val libraryProvidedLinkerFlags = context.llvm.allNativeDependencies.map { it.linkerOpts }.flatten()
if (context.config.produce.isCache) {
context.config.outputFiles.tempCacheDirectory!!.mkdirs()
saveAdditionalInfoForCache()
}
runLinker(objectFiles, includedBinaries, libraryProvidedLinkerFlags) runLinker(objectFiles, includedBinaries, libraryProvidedLinkerFlags)
renameOutput()
}
private fun saveAdditionalInfoForCache() {
saveCacheBitcodeDependencies()
saveInlineFunctionBodies()
saveClassFields()
}
private fun saveCacheBitcodeDependencies() {
val outputFiles = context.config.outputFiles
val bitcodeDependenciesFile = File(outputFiles.bitcodeDependenciesFile!!)
val bitcodeDependencies = context.config.resolvedLibraries
.getFullList(TopologicalLibraryOrder)
.filter {
require(it is KonanLibrary)
context.llvmImports.bitcodeIsUsed(it)
&& it !in context.config.cacheSupport.librariesToCache // Skip loops.
}.cast<List<KonanLibrary>>()
bitcodeDependenciesFile.writeLines(bitcodeDependencies.map { it.uniqueName })
}
private fun saveInlineFunctionBodies() {
val outputFiles = context.config.outputFiles
val inlineFunctionBodiesFile = File(outputFiles.inlineFunctionBodiesFile!!)
inlineFunctionBodiesFile.writeBytes(InlineFunctionBodyReferenceSerializer.serialize(context.inlineFunctionBodies))
}
private fun saveClassFields() {
val outputFiles = context.config.outputFiles
val classFieldsFile = File(outputFiles.classFieldsFile!!)
classFieldsFile.writeBytes(ClassFieldsSerializer.serialize(context.classFields))
}
private fun renameOutput() {
if (context.config.produce.isCache) {
val outputFiles = context.config.outputFiles
// For caches the output file is a directory. It might be created by someone else,
// We have to delete it in order to the next renaming operation to succeed.
java.io.File(outputFiles.mainFile).delete()
if (!java.io.File(outputFiles.tempCacheDirectory!!.absolutePath).renameTo(java.io.File(outputFiles.mainFile)))
outputFiles.tempCacheDirectory.deleteRecursively()
}
} }
private fun asLinkerArgs(args: List<String>): List<String> { private fun asLinkerArgs(args: List<String>): List<String> {
@@ -255,11 +247,11 @@ private fun determineCachesToLink(context: Context): CachesToLink {
error("Library ${library.libraryName} is found in both cache and current binary") error("Library ${library.libraryName} is found in both cache and current binary")
val list = when (cache.kind) { val list = when (cache.kind) {
CachedLibraries.Cache.Kind.DYNAMIC -> dynamicCaches CachedLibraries.Kind.DYNAMIC -> dynamicCaches
CachedLibraries.Cache.Kind.STATIC -> staticCaches CachedLibraries.Kind.STATIC -> staticCaches
} }
list += cache.path list += cache.binariesPaths
} }
return CachesToLink(static = staticCaches, dynamic = dynamicCaches) return CachesToLink(static = staticCaches, dynamic = dynamicCaches)
} }
@@ -7,10 +7,8 @@ package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.ir.konanLibrary import org.jetbrains.kotlin.backend.konan.ir.konanLibrary
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.util.getPackageFragment
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
internal abstract class LlvmModuleSpecificationBase(protected val cachedLibraries: CachedLibraries) : LlvmModuleSpecification { internal abstract class LlvmModuleSpecificationBase(protected val cachedLibraries: CachedLibraries) : LlvmModuleSpecification {
@@ -31,6 +29,8 @@ internal abstract class LlvmModuleSpecificationBase(protected val cachedLibrarie
override fun containsDeclaration(declaration: IrDeclaration): Boolean = override fun containsDeclaration(declaration: IrDeclaration): Boolean =
declaration.konanLibrary.let { it == null || containsLibrary(it) } declaration.konanLibrary.let { it == null || containsLibrary(it) }
// When producing per-file caches, some declarations might be generated by the stub generator.
&& declaration.getPackageFragment() !is IrExternalPackageFragment
} }
internal class DefaultLlvmModuleSpecification(cachedLibraries: CachedLibraries) internal class DefaultLlvmModuleSpecification(cachedLibraries: CachedLibraries)
@@ -42,9 +42,9 @@ internal class DefaultLlvmModuleSpecification(cachedLibraries: CachedLibraries)
internal class CacheLlvmModuleSpecification( internal class CacheLlvmModuleSpecification(
cachedLibraries: CachedLibraries, cachedLibraries: CachedLibraries,
private val librariesToCache: Set<KotlinLibrary> private val libraryToCache: KotlinLibrary
) : LlvmModuleSpecificationBase(cachedLibraries) { ) : LlvmModuleSpecificationBase(cachedLibraries) {
override val isFinal = false override val isFinal = false
override fun containsLibrary(library: KotlinLibrary): Boolean = library in librariesToCache override fun containsLibrary(library: KotlinLibrary): Boolean = library == libraryToCache
} }
@@ -17,7 +17,7 @@ import kotlin.random.Random
/** /**
* Creates and stores terminal compiler outputs. * Creates and stores terminal compiler outputs.
*/ */
class OutputFiles(outputPath: String?, target: KonanTarget, val produce: CompilerOutputKind) { class OutputFiles(outputPath: String?, target: KonanTarget, val produce: CompilerOutputKind, producePerFileCache: Boolean) {
private val prefix = produce.prefix(target) private val prefix = produce.prefix(target)
private val suffix = produce.suffix(target) private val suffix = produce.suffix(target)
@@ -34,44 +34,41 @@ class OutputFiles(outputPath: String?, target: KonanTarget, val produce: Compile
val cAdapterDef by lazy { File("${outputName}.def") } val cAdapterDef by lazy { File("${outputName}.def") }
/** /**
* Main compiler's output file. * Compiler's main output file.
*/ */
val mainFile = val mainFileName =
if (produce.isCache) if (produce.isCache)
outputName outputName
else else
outputName.fullOutputName() outputName.fullOutputName()
private val cacheFile = File(outputName.fullOutputName()).absoluteFile.name val mainFile = File(mainFileName)
val dynamicCacheInstallName = File(outputName).child(cacheFile).absolutePath private val pathToPerFileCache =
if (producePerFileCache)
outputName.substring(0, outputName.lastIndexOf(File.separatorChar) /* skip [PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME]*/)
else null
val perFileCacheFileName = pathToPerFileCache?.let { File(it).name }
val cacheFileName = File((pathToPerFileCache ?: outputName).fullOutputName()).absoluteFile.name
val dynamicCacheInstallName = File(outputName).child(cacheFileName).absolutePath
val tempCacheDirectory = val tempCacheDirectory =
if (produce.isCache) if (produce.isCache)
File(outputName + Random.nextLong().toString()) File(outputName + Random.nextLong().toString())
else null else null
val nativeBinaryFile = val nativeBinaryFile = tempCacheDirectory?.child(cacheFileName)?.absolutePath ?: mainFileName
if (produce.isCache)
tempCacheDirectory!!.child(cacheFile).absolutePath
else mainFile
val symbolicInfoFile = "$nativeBinaryFile.dSYM" val symbolicInfoFile = "$nativeBinaryFile.dSYM"
val bitcodeDependenciesFile = val bitcodeDependenciesFile = tempCacheDirectory?.child(CachedLibraries.BITCODE_DEPENDENCIES_FILE_NAME)
if (produce.isCache)
tempCacheDirectory!!.child(CachedLibraries.BITCODE_DEPENDENCIES_FILE_NAME).absolutePath
else null
val inlineFunctionBodiesFile = val inlineFunctionBodiesFile = tempCacheDirectory?.child(CachedLibraries.INLINE_FUNCTION_BODIES_FILE_NAME)
if (produce.isCache)
tempCacheDirectory!!.child(CachedLibraries.INLINE_FUNCTION_BODIES_FILE_NAME).absolutePath
else null
val classFieldsFile = val classFieldsFile = tempCacheDirectory?.child(CachedLibraries.CLASS_FIELDS_FILE_NAME)
if (produce.isCache)
tempCacheDirectory!!.child(CachedLibraries.CLASS_FIELDS_FILE_NAME).absolutePath
else null
private fun String.fullOutputName() = prefixBaseNameIfNeeded(prefix).suffixIfNeeded(suffix) private fun String.fullOutputName() = prefixBaseNameIfNeeded(prefix).suffixIfNeeded(suffix)
@@ -64,10 +64,10 @@ internal fun Context.psiToIr(
) )
val irBuiltInsOverDescriptors = generatorContext.irBuiltIns as IrBuiltInsOverDescriptors val irBuiltInsOverDescriptors = generatorContext.irBuiltIns as IrBuiltInsOverDescriptors
val functionIrClassFactory: KonanIrAbstractDescriptorBasedFunctionFactory = val functionIrClassFactory: KonanIrAbstractDescriptorBasedFunctionFactory =
if (config.lazyIrForCaches && !llvmModuleSpecification.containsModule(this.stdlibModule)) if (shouldDefineFunctionClasses || (!config.lazyIrForCaches && !config.producePerFileCache))
LazyIrFunctionFactory(symbolTable, stubGenerator, irBuiltInsOverDescriptors, reflectionTypes)
else
BuiltInFictitiousFunctionIrClassFactory(symbolTable, irBuiltInsOverDescriptors, reflectionTypes) BuiltInFictitiousFunctionIrClassFactory(symbolTable, irBuiltInsOverDescriptors, reflectionTypes)
else
LazyIrFunctionFactory(symbolTable, stubGenerator, irBuiltInsOverDescriptors, reflectionTypes)
irBuiltInsOverDescriptors.functionFactory = functionIrClassFactory irBuiltInsOverDescriptors.functionFactory = functionIrClassFactory
val symbols = KonanSymbols(this, generatorContext.irBuiltIns, symbolTable, symbolTable.lazyWrapper) val symbols = KonanSymbols(this, generatorContext.irBuiltIns, symbolTable, symbolTable.lazyWrapper)
@@ -144,10 +144,11 @@ internal fun Context.psiToIr(
for (dependency in sortDependencies(dependencies).filter { it != moduleDescriptor }) { for (dependency in sortDependencies(dependencies).filter { it != moduleDescriptor }) {
val kotlinLibrary = (dependency.getCapability(KlibModuleOrigin.CAPABILITY) as? DeserializedKlibModuleOrigin)?.library val kotlinLibrary = (dependency.getCapability(KlibModuleOrigin.CAPABILITY) as? DeserializedKlibModuleOrigin)?.library
val isCachedLibrary = kotlinLibrary != null && config.cachedLibraries.isLibraryCached(kotlinLibrary) val isFullyCachedLibrary = kotlinLibrary != null &&
if (isProducingLibrary || (config.lazyIrForCaches && isCachedLibrary)) config.cachedLibraries.isLibraryCached(kotlinLibrary) && kotlinLibrary != config.libraryToCache?.klib
if (isProducingLibrary || (config.lazyIrForCaches && isFullyCachedLibrary))
linker.deserializeOnlyHeaderModule(dependency, kotlinLibrary) linker.deserializeOnlyHeaderModule(dependency, kotlinLibrary)
else if (isCachedLibrary) else if (isFullyCachedLibrary)
linker.deserializeHeadersWithInlineBodies(dependency, kotlinLibrary!!) linker.deserializeHeadersWithInlineBodies(dependency, kotlinLibrary!!)
else else
linker.deserializeIrModuleHeader(dependency, kotlinLibrary, dependency.name.asString()) linker.deserializeIrModuleHeader(dependency, kotlinLibrary, dependency.name.asString())
@@ -217,6 +218,11 @@ internal fun Context.psiToIr(
// Note: coupled with [shouldLower] below. // Note: coupled with [shouldLower] below.
irModules = modules.filterValues { llvmModuleSpecification.containsModule(it) } irModules = modules.filterValues { llvmModuleSpecification.containsModule(it) }
// IR linker deserializes files in the order they lie on the disk, which might be inconvenient,
// so to make the pipeline more deterministic, the files are to be sorted.
// This concerns in the first place global initializers order for the eager initialization strategy,
// where the files are being initialized in order one by one.
irModules.values.forEach { module -> module.files.sortBy { it.fileEntry.name } }
if (!isProducingLibrary) if (!isProducingLibrary)
irLinker = irDeserializer as KonanIrLinker irLinker = irDeserializer as KonanIrLinker
@@ -226,7 +232,7 @@ internal fun Context.psiToIr(
if (!isProducingLibrary) { if (!isProducingLibrary) {
// TODO: find out what should be done in the new builtins/symbols about it // TODO: find out what should be done in the new builtins/symbols about it
if (this.stdlibModule in modulesWithoutDCE) { if (this.stdlibModule in modulesWithoutDCE) {
(functionIrClassFactory as BuiltInFictitiousFunctionIrClassFactory).buildAllClasses() (functionIrClassFactory as? BuiltInFictitiousFunctionIrClassFactory)?.buildAllClasses()
} }
internalAbi.init(irModules.values + irModule!!) internalAbi.init(irModules.values + irModule!!)
(functionIrClassFactory as? BuiltInFictitiousFunctionIrClassFactory)?.module = (functionIrClassFactory as? BuiltInFictitiousFunctionIrClassFactory)?.module =
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.serialization.CompatibilityMode
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataMonolithicSerializer import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataMonolithicSerializer
import org.jetbrains.kotlin.backend.konan.descriptors.isFromInteropLibrary import org.jetbrains.kotlin.backend.konan.descriptors.isFromInteropLibrary
import org.jetbrains.kotlin.backend.konan.llvm.* import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.lower.CacheInfoBuilder
import org.jetbrains.kotlin.backend.konan.lower.ExpectToActualDefaultValueCopier import org.jetbrains.kotlin.backend.konan.lower.ExpectToActualDefaultValueCopier
import org.jetbrains.kotlin.backend.konan.lower.SamSuperTypesChecker import org.jetbrains.kotlin.backend.konan.lower.SamSuperTypesChecker
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
@@ -22,15 +23,10 @@ import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
import org.jetbrains.kotlin.ir.expressions.IrGetField
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -133,33 +129,7 @@ internal val buildAdditionalCacheInfoPhase = konanUnitPhase(
if (moduleDeserializer == null) { if (moduleDeserializer == null) {
require(module.descriptor.isFromInteropLibrary()) { "No module deserializer for ${module.descriptor}" } require(module.descriptor.isFromInteropLibrary()) { "No module deserializer for ${module.descriptor}" }
} else { } else {
val compatibleMode = CompatibilityMode(moduleDeserializer.libraryAbiVersion).oldSignatures CacheInfoBuilder(this, moduleDeserializer).build()
module.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitClass(declaration: IrClass) {
declaration.acceptChildrenVoid(this)
if (!declaration.isInterface && declaration.visibility != DescriptorVisibilities.LOCAL
&& declaration.isExported && declaration.origin != DECLARATION_ORIGIN_FUNCTION_CLASS)
classFields.add(moduleDeserializer.buildClassFields(declaration, getLayoutBuilder(declaration).getDeclaredFields()))
}
override fun visitFunction(declaration: IrFunction) {
declaration.acceptChildrenVoid(this)
if (declaration.isFakeOverride || !declaration.isExportedInlineFunction) return
inlineFunctionBodies.add(moduleDeserializer.buildInlineFunctionReference(declaration))
}
private val IrClass.isExported
get() = with(KonanManglerIr) { isExported(compatibleMode) }
private val IrFunction.isExportedInlineFunction
get() = isInline && with(KonanManglerIr) { isExported(compatibleMode) }
})
} }
} }
}, },
@@ -240,6 +210,12 @@ internal val serializerPhase = konanUnitPhase(
description = "Serialize descriptor tree and inline IR bodies" description = "Serialize descriptor tree and inline IR bodies"
) )
internal val saveAdditionalCacheInfoPhase = konanUnitPhase(
op = { CacheStorage(this).saveAdditionalCacheInfo() },
name = "SaveAdditionalCacheInfo",
description = "Save additional cache info (inline functions bodies and fields of classes)"
)
internal val objectFilesPhase = konanUnitPhase( internal val objectFilesPhase = konanUnitPhase(
op = { compilerOutput = BitcodeCompiler(this).makeObjectFiles(bitcodeFileName) }, op = { compilerOutput = BitcodeCompiler(this).makeObjectFiles(bitcodeFileName) },
name = "ObjectFiles", name = "ObjectFiles",
@@ -252,6 +228,12 @@ internal val linkerPhase = konanUnitPhase(
description = "Linker" description = "Linker"
) )
internal val finalizeCachePhase = konanUnitPhase(
op = { CacheStorage(this).renameOutput() },
name = "FinalizeCache",
description = "Finalize cache (rename temp to the final dist)"
)
internal val allLoweringsPhase = NamedCompilerPhase( internal val allLoweringsPhase = NamedCompilerPhase(
name = "IrLowering", name = "IrLowering",
description = "IR Lowering", description = "IR Lowering",
@@ -407,6 +389,9 @@ internal val exportInternalAbiPhase = makeKonanModuleOpPhase(
override fun visitClass(declaration: IrClass) { override fun visitClass(declaration: IrClass) {
declaration.acceptChildrenVoid(this) declaration.acceptChildrenVoid(this)
if (declaration.isLocal) return
if (declaration.isCompanion) { if (declaration.isCompanion) {
val function = context.irFactory.buildFun { val function = context.irFactory.buildFun {
name = InternalAbi.getCompanionObjectAccessorName(declaration) name = InternalAbi.getCompanionObjectAccessorName(declaration)
@@ -595,8 +580,10 @@ val toplevelPhase: CompilerPhase<*, Unit, Unit> = namedUnitPhase(
disposeLLVMPhase then disposeLLVMPhase then
unitSink() unitSink()
) then ) then
saveAdditionalCacheInfoPhase then
objectFilesPhase then objectFilesPhase then
linkerPhase linkerPhase then
finalizeCachePhase
) )
internal fun PhaseConfig.disableIf(phase: AnyNamedPhase, condition: Boolean) { internal fun PhaseConfig.disableIf(phase: AnyNamedPhase, condition: Boolean) {
@@ -621,8 +608,10 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
disableUnless(serializerPhase, config.produce == CompilerOutputKind.LIBRARY) disableUnless(serializerPhase, config.produce == CompilerOutputKind.LIBRARY)
disableUnless(entryPointPhase, config.produce == CompilerOutputKind.PROGRAM) disableUnless(entryPointPhase, config.produce == CompilerOutputKind.PROGRAM)
disableUnless(buildAdditionalCacheInfoPhase, config.produce.isCache && config.lazyIrForCaches) disableUnless(buildAdditionalCacheInfoPhase, config.produce.isCache && config.lazyIrForCaches)
disableUnless(saveAdditionalCacheInfoPhase, config.produce.isCache && config.lazyIrForCaches)
disableUnless(finalizeCachePhase, config.produce.isCache)
disableUnless(exportInternalAbiPhase, config.produce.isCache) disableUnless(exportInternalAbiPhase, config.produce.isCache)
disableIf(backendCodegen, config.produce == CompilerOutputKind.LIBRARY || config.omitFrameworkBinary) disableIf(backendCodegen, config.produce == CompilerOutputKind.LIBRARY || config.omitFrameworkBinary || config.produce == CompilerOutputKind.PRELIMINARY_CACHE)
disableUnless(checkExternalCallsPhase, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) disableUnless(checkExternalCallsPhase, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS))
disableUnless(rewriteExternalCallsCheckerGlobals, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)) disableUnless(rewriteExternalCallsCheckerGlobals, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS))
disableUnless(stringConcatenationTypeNarrowingPhase, config.optimizationsEnabled) disableUnless(stringConcatenationTypeNarrowingPhase, config.optimizationsEnabled)
@@ -279,7 +279,7 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
context.getLayoutBuilder(superClass).vtableEntries context.getLayoutBuilder(superClass).vtableEntries
} }
val methods = irClass.overridableOrOverridingMethods val methods = overridableOrOverridingMethods
val newVtableSlots = mutableListOf<OverriddenFunctionInfo>() val newVtableSlots = mutableListOf<OverriddenFunctionInfo>()
val overridenVtableSlots = mutableMapOf<IrSimpleFunction, OverriddenFunctionInfo>() val overridenVtableSlots = mutableMapOf<IrSimpleFunction, OverriddenFunctionInfo>()
@@ -349,7 +349,7 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
} }
fun overridingOf(function: IrSimpleFunction) = fun overridingOf(function: IrSimpleFunction) =
irClass.overridableOrOverridingMethods.firstOrNull { function in it.allOverriddenFunctions }?.let { overridableOrOverridingMethods.firstOrNull { function in it.allOverriddenFunctions }?.let {
OverriddenFunctionInfo(it, function).getImplementation(context) OverriddenFunctionInfo(it, function).getImplementation(context)
} }
@@ -468,13 +468,15 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
val outerThisField = if (irClass.isInner) val outerThisField = if (irClass.isInner)
context.specialDeclarationsFactory.getOuterThisField(irClass) context.specialDeclarationsFactory.getOuterThisField(irClass)
else null else null
if (context.config.lazyIrForCaches && !context.llvmModuleSpecification.containsDeclaration(irClass)) { val packageFragment = irClass.getPackageFragment()
val packageFragment = irClass.getPackageFragment() if (packageFragment is IrExternalPackageFragment) {
val moduleDescriptor = packageFragment.packageFragmentDescriptor.containingDeclaration val moduleDescriptor = packageFragment.packageFragmentDescriptor.containingDeclaration
if (moduleDescriptor.isFromInteropLibrary()) if (moduleDescriptor.isFromInteropLibrary()) return emptyList()
return emptyList()
val moduleDeserializer = context.irLinker.moduleDeserializers[moduleDescriptor] val moduleDeserializer = context.irLinker.moduleDeserializers[moduleDescriptor]
?: error("No module deserializer for ${irClass.render()}") ?: error("No module deserializer for ${irClass.render()}")
require(context.config.cachedLibraries.isLibraryCached(moduleDeserializer.klib)) {
"No IR and no cache for ${irClass.render()}"
}
return moduleDeserializer.deserializeClassFields(irClass, outerThisField) return moduleDeserializer.deserializeClassFields(irClass, outerThisField)
} }
@@ -492,8 +494,8 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
} }
} }
private val IrClass.overridableOrOverridingMethods: List<IrSimpleFunction> private val overridableOrOverridingMethods: List<IrSimpleFunction>
get() = this.simpleFunctions().filter { it.isOverridableOrOverrides && it.bridgeTarget == null } get() = irClass.simpleFunctions().filter { it.isOverridableOrOverrides && it.bridgeTarget == null }
private val IrFunction.uniqueName get() = computeFunctionName() private val IrFunction.uniqueName get() = computeFunctionName()
} }
@@ -123,6 +123,10 @@ internal class KonanSymbols(
val interopCOpaque = symbolTable.referenceClass(context.interopBuiltIns.cOpaque) val interopCOpaque = symbolTable.referenceClass(context.interopBuiltIns.cOpaque)
val interopObjCObject = symbolTable.referenceClass(context.interopBuiltIns.objCObject) val interopObjCObject = symbolTable.referenceClass(context.interopBuiltIns.objCObject)
val interopObjCObjectBase = symbolTable.referenceClass(context.interopBuiltIns.objCObjectBase) val interopObjCObjectBase = symbolTable.referenceClass(context.interopBuiltIns.objCObjectBase)
val interopObjCObjectBaseMeta = symbolTable.referenceClass(context.interopBuiltIns.objCObjectBaseMeta)
val interopObjCClass = symbolTable.referenceClass(context.interopBuiltIns.objCClass)
val interopObjCClassOf = symbolTable.referenceClass(context.interopBuiltIns.objCClassOf)
val interopObjCProtocol = symbolTable.referenceClass(context.interopBuiltIns.objCProtocol)
val interopObjCRelease = interopFunction("objc_release") val interopObjCRelease = interopFunction("objc_release")
@@ -175,8 +179,14 @@ internal class KonanSymbols(
val interopObjCGetSelector = interopFunction("objCGetSelector") val interopObjCGetSelector = interopFunction("objCGetSelector")
val interopCEnum = interopClass("CEnum")
val interopCPrimitiveVar = interopClass("CPrimitiveVar")
val interopCEnumVar = interopClass("CEnumVar") val interopCEnumVar = interopClass("CEnumVar")
val interopCStructVar = interopClass("CStructVar")
val nativeMemUtils = symbolTable.referenceClass(context.interopBuiltIns.nativeMemUtils) val nativeMemUtils = symbolTable.referenceClass(context.interopBuiltIns.nativeMemUtils)
val nativeHeap = symbolTable.referenceClass(context.interopBuiltIns.nativeHeap) val nativeHeap = symbolTable.referenceClass(context.interopBuiltIns.nativeHeap)
@@ -19,7 +19,6 @@ import org.jetbrains.kotlin.backend.konan.serialization.AbstractKonanIrMangler
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.findAnnotation import org.jetbrains.kotlin.ir.util.findAnnotation
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.konan.library.KonanLibrary import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.library.uniqueName import org.jetbrains.kotlin.library.uniqueName
@@ -37,22 +36,28 @@ object KonanBinaryInterface {
val IrFunction.functionName: String get() = mangler.run { signatureString(compatibleMode = true) } val IrFunction.functionName: String get() = mangler.run { signatureString(compatibleMode = true) }
val IrFunction.symbolName: String get() = funSymbolNameImpl() val IrFunction.symbolName: String
val IrField.symbolName: String get() = get() {
withPrefix(MangleConstant.FIELD_PREFIX, fieldSymbolNameImpl()) require(isExported(this)) { "Asked for symbol name for a private function ${render()}" }
val IrClass.typeInfoSymbolName: String get() =
withPrefix(MangleConstant.CLASS_PREFIX, typeInfoSymbolNameImpl()) return funSymbolNameImpl(null)
}
val IrField.symbolName: String get() = withPrefix(MangleConstant.FIELD_PREFIX, fieldSymbolNameImpl())
val IrClass.typeInfoSymbolName: String get() = typeInfoSymbolNameImpl(null)
fun IrFunction.privateSymbolName(containerName: String): String = funSymbolNameImpl(containerName)
fun IrClass.privateTypeInfoSymbolName(containerName: String): String = typeInfoSymbolNameImpl(containerName)
fun isExported(declaration: IrDeclaration) = exportChecker.run { fun isExported(declaration: IrDeclaration) = exportChecker.run {
check(declaration, SpecialDeclarationType.REGULAR) || declaration.isPlatformSpecificExported() check(declaration, SpecialDeclarationType.REGULAR) || declaration.isPlatformSpecificExported()
} }
private fun withPrefix(prefix: String, mangle: String) = "$prefix:$mangle" private fun withPrefix(prefix: String, mangle: String) = "$prefix:$mangle"
private fun IrFunction.funSymbolNameImpl(): String { private fun IrFunction.funSymbolNameImpl(containerName: String?): String {
if (!isExported(this)) {
throw AssertionError(render())
}
if (isExternal) { if (isExternal) {
this.externalSymbolOrThrow()?.let { this.externalSymbolOrThrow()?.let {
return it return it
@@ -64,7 +69,8 @@ object KonanBinaryInterface {
return name // no wrapping currently required return name // no wrapping currently required
} }
return withPrefix(MangleConstant.FUN_PREFIX, mangler.run { mangleString(compatibleMode = true) }) val mangle = mangler.run { mangleString(compatibleMode = true) }
return withPrefix(MangleConstant.FUN_PREFIX, containerName?.plus(".$mangle") ?: mangle)
} }
private fun IrField.fieldSymbolNameImpl(): String { private fun IrField.fieldSymbolNameImpl(): String {
@@ -74,8 +80,9 @@ object KonanBinaryInterface {
return "$containingDeclarationPart$name" return "$containingDeclarationPart$name"
} }
private fun IrClass.typeInfoSymbolNameImpl(): String { private fun IrClass.typeInfoSymbolNameImpl(containerName: String?): String {
return this.fqNameForIrSerialization.toString() val fqName = fqNameForIrSerialization.toString()
return withPrefix(MangleConstant.CLASS_PREFIX, containerName?.plus(".$fqName") ?: fqName)
} }
} }
@@ -117,10 +124,14 @@ fun IrFunction.computeFullName() = parent.fqNameForIrSerialization.child(Name.id
fun IrFunction.computeSymbolName() = with(KonanBinaryInterface) { symbolName }.replaceSpecialSymbols() fun IrFunction.computeSymbolName() = with(KonanBinaryInterface) { symbolName }.replaceSpecialSymbols()
fun IrFunction.computePrivateSymbolName(containerName: String) = with(KonanBinaryInterface) { privateSymbolName(containerName) }.replaceSpecialSymbols()
fun IrField.computeSymbolName() = with(KonanBinaryInterface) { symbolName }.replaceSpecialSymbols() fun IrField.computeSymbolName() = with(KonanBinaryInterface) { symbolName }.replaceSpecialSymbols()
fun IrClass.computeTypeInfoSymbolName() = with(KonanBinaryInterface) { typeInfoSymbolName }.replaceSpecialSymbols() fun IrClass.computeTypeInfoSymbolName() = with(KonanBinaryInterface) { typeInfoSymbolName }.replaceSpecialSymbols()
fun IrClass.computePrivateTypeInfoSymbolName(containerName: String) = with(KonanBinaryInterface) { privateTypeInfoSymbolName(containerName) }.replaceSpecialSymbols()
private fun String.replaceSpecialSymbols() = private fun String.replaceSpecialSymbols() =
// '@' is used for symbol versioning in GCC: https://gcc.gnu.org/wiki/SymbolVersioning. // '@' is used for symbol versioning in GCC: https://gcc.gnu.org/wiki/SymbolVersioning.
this.replace("@", "__at__") this.replace("@", "__at__")
@@ -514,8 +514,8 @@ internal abstract class FunctionGenerationContext(
init { init {
irFunction?.let { irFunction?.let {
if (!irFunction.isExported()) { if (!irFunction.isExported()) {
LLVMSetLinkage(function, LLVMLinkage.LLVMInternalLinkage) if (!context.config.producePerFileCache || irFunction !in context.calledFromExportedInlineFunctions)
// (Cannot do this before the function body is created). LLVMSetLinkage(function, LLVMLinkage.LLVMInternalLinkage) // (Cannot do this before the function body is created)
} }
} }
} }
@@ -7,19 +7,18 @@ package org.jetbrains.kotlin.backend.konan.llvm
import kotlinx.cinterop.toKString import kotlinx.cinterop.toKString
import llvm.* import llvm.*
import org.jetbrains.kotlin.backend.konan.BoxCache import org.jetbrains.kotlin.backend.common.serialization.mangle.MangleConstant
import org.jetbrains.kotlin.backend.konan.CachedLibraries import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.TargetAbiInfo import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
import org.jetbrains.kotlin.backend.konan.llvm.KonanBinaryInterface.functionName
import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.CurrentKlibModuleOrigin import org.jetbrains.kotlin.descriptors.konan.CurrentKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.file import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyFunction
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.isReal
import org.jetbrains.kotlin.konan.library.KonanLibrary import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
@@ -183,7 +182,14 @@ internal interface ContextUtils : RuntimeAware {
} }
return if (isExternal(this)) { return if (isExternal(this)) {
runtime.addedLLVMExternalFunctions.getOrPut(this) { runtime.addedLLVMExternalFunctions.getOrPut(this) {
val proto = LlvmFunctionProto(this, this.computeSymbolName(), this@ContextUtils) val symbolName = if (KonanBinaryInterface.isExported(this)) {
this.computeSymbolName()
} else {
val containerName = parentClassOrNull?.fqNameForIrSerialization?.asString()
?: context.irLinker.getExternalDeclarationFileName(this)
this.computePrivateSymbolName(containerName)
}
val proto = LlvmFunctionProto(this, symbolName, this@ContextUtils)
context.llvm.externalFunction(proto) context.llvm.externalFunction(proto)
} }
} else { } else {
@@ -203,7 +209,13 @@ internal interface ContextUtils : RuntimeAware {
val IrClass.typeInfoPtr: ConstPointer val IrClass.typeInfoPtr: ConstPointer
get() { get() {
return if (isExternal(this)) { return if (isExternal(this)) {
constPointer(importGlobal(this.computeTypeInfoSymbolName(), runtime.typeInfoType, val typeInfoSymbolName = if (KonanBinaryInterface.isExported(this)) {
this.computeTypeInfoSymbolName()
} else {
this.computePrivateTypeInfoSymbolName(context.irLinker.getExternalDeclarationFileName(this))
}
constPointer(importGlobal(typeInfoSymbolName, runtime.typeInfoType,
origin = this.llvmSymbolOrigin)) origin = this.llvmSymbolOrigin))
} else { } else {
context.llvmDeclarations.forClass(this).typeInfo context.llvmDeclarations.forClass(this).typeInfo
@@ -379,6 +391,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) : Runti
} }
for (library in immediateBitcodeDependencies) { for (library in immediateBitcodeDependencies) {
if (library == context.config.libraryToCache?.klib) continue
val cache = context.config.cachedLibraries.getLibraryCache(library) val cache = context.config.cachedLibraries.getLibraryCache(library)
if (cache != null) { if (cache != null) {
result += library result += library
@@ -395,7 +408,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) : Runti
val allBitcodeDependencies: List<KonanLibrary> by lazy { val allBitcodeDependencies: List<KonanLibrary> by lazy {
val allNonCachedDependencies = context.librariesWithDependencies.filter { val allNonCachedDependencies = context.librariesWithDependencies.filter {
context.config.cachedLibraries.getLibraryCache(it) == null context.config.cachedLibraries.getLibraryCache(it) == null || it == context.config.libraryToCache?.klib
} }
val set = (allNonCachedDependencies + allCachedBitcodeDependencies).toSet() val set = (allNonCachedDependencies + allCachedBitcodeDependencies).toSet()
// This list is used in particular to build the libraries' initializers chain. // This list is used in particular to build the libraries' initializers chain.
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.konan.ForeignExceptionMode
import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.classId
@@ -2816,35 +2817,52 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
initializers.add(it.initializer) initializers.add(it.initializer)
} }
val ctorFunctions = libraries.map { library -> fun fileCtorName(libraryName: String, fileName: String) = "$libraryName:$fileName".moduleConstructorName
val ctorName = if (library != null) {
library.moduleConstructorName
} else {
context.config.moduleId.moduleConstructorName
}
val ctorFunction = addLlvmFunctionWithDefaultAttributes( fun addCtorFunction(ctorName: String) =
context, addLlvmFunctionWithDefaultAttributes(
context.llvmModule!!, context,
ctorName, context.llvmModule!!,
kVoidFuncType ctorName,
) kVoidFuncType
LLVMSetLinkage(ctorFunction, LLVMLinkage.LLVMExternalLinkage) ).also { LLVMSetLinkage(it, LLVMLinkage.LLVMExternalLinkage) }
val ctorFunctions = libraries.flatMap { library ->
val initializers = libraryToInitializers.getValue(library) val initializers = libraryToInitializers.getValue(library)
if (library == null || context.llvmModuleSpecification.containsLibrary(library)) { val ctorName = when {
val otherInitializers = library == null -> context.config.moduleId.moduleConstructorName
context.llvm.otherStaticInitializers.takeIf { library == null }.orEmpty() library == context.config.libraryToCache?.klib
&& context.config.producePerFileCache ->
fileCtorName(library.uniqueName, context.config.outputFiles.perFileCacheFileName!!)
else -> library.moduleConstructorName
}
if (library == null || context.llvmModuleSpecification.containsLibrary(library)) {
val otherInitializers = context.llvm.otherStaticInitializers.takeIf { library == null }.orEmpty()
val ctorFunction = addCtorFunction(ctorName)
appendStaticInitializers(ctorFunction, initializers + otherInitializers) appendStaticInitializers(ctorFunction, initializers + otherInitializers)
listOf(ctorFunction)
} else { } else {
// A cached library.
check(initializers.isEmpty()) { check(initializers.isEmpty()) {
"found initializer from ${library.libraryFile}, which is not included into compilation" "found initializer from ${library.libraryFile}, which is not included into compilation"
} }
}
ctorFunction val cache = context.config.cachedLibraries.getLibraryCache(library)
?: error("Library $library is expected to be cached")
when (cache.granularity) {
CachedLibraries.Granularity.MODULE -> listOf(addCtorFunction(ctorName))
CachedLibraries.Granularity.FILE -> {
context.irLinker.klibToModuleDeserializerMap[library]!!.sortedFileIds.map {
addCtorFunction(fileCtorName(library.uniqueName, it))
}
}
}
}
} }
appendGlobalCtors(ctorFunctions) appendGlobalCtors(ctorFunctions)
@@ -7,12 +7,17 @@ package org.jetbrains.kotlin.backend.konan.llvm
import kotlinx.cinterop.toCValues import kotlinx.cinterop.toCValues
import llvm.* import llvm.*
import org.jetbrains.kotlin.backend.common.serialization.mangle.MangleConstant
import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.ClassLayoutBuilder import org.jetbrains.kotlin.backend.konan.descriptors.ClassLayoutBuilder
import org.jetbrains.kotlin.backend.konan.descriptors.isTypedIntrinsic import org.jetbrains.kotlin.backend.konan.descriptors.isTypedIntrinsic
import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.KonanBinaryInterface.functionName
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
@@ -170,7 +175,12 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
val typeInfoSymbolName = if (declaration.isExported()) { val typeInfoSymbolName = if (declaration.isExported()) {
declaration.computeTypeInfoSymbolName() declaration.computeTypeInfoSymbolName()
} else { } else {
"ktype:$internalName" if (!context.config.producePerFileCache)
"${MangleConstant.CLASS_PREFIX}:$internalName"
else {
val containerName = (context.config.libraryToCache!!.strategy as CacheDeserializationStrategy.SingleFile).filePath
declaration.computePrivateTypeInfoSymbolName(containerName)
}
} }
if (declaration.typeInfoHasVtableAttached) { if (declaration.typeInfoHasVtableAttached) {
@@ -196,7 +206,8 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
throw IllegalArgumentException("Global '$typeInfoSymbolName' already exists") throw IllegalArgumentException("Global '$typeInfoSymbolName' already exists")
} }
} else { } else {
LLVMSetLinkage(llvmTypeInfoPtr, LLVMLinkage.LLVMInternalLinkage) if (!context.config.producePerFileCache || declaration !in context.constructedFromExportedInlineFunctions)
LLVMSetLinkage(llvmTypeInfoPtr, LLVMLinkage.LLVMInternalLinkage)
} }
typeInfoPtr = constPointer(llvmTypeInfoPtr) typeInfoPtr = constPointer(llvmTypeInfoPtr)
@@ -365,8 +376,15 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
} }
} }
} else { } else {
"kfun:" + qualifyInternalName(declaration) if (!context.config.producePerFileCache)
"${MangleConstant.FUN_PREFIX}:${qualifyInternalName(declaration)}"
else {
val containerName = declaration.parentClassOrNull?.fqNameForIrSerialization?.asString()
?: (context.config.libraryToCache!!.strategy as CacheDeserializationStrategy.SingleFile).filePath
declaration.computePrivateSymbolName(containerName)
}
} }
val proto = LlvmFunctionProto(declaration, symbolName, this) val proto = LlvmFunctionProto(declaration, symbolName, this)
val llvmFunction = addLlvmFunctionWithDefaultAttributes( val llvmFunction = addLlvmFunctionWithDefaultAttributes(
context, context,
@@ -287,7 +287,7 @@ internal class ObjCExportBlockCodeGenerator(codegen: CodeGenerator) : ObjCExport
// 1. Enumerates [BuiltInFictitiousFunctionIrClassFactory] built classes, which may be incomplete otherwise. // 1. Enumerates [BuiltInFictitiousFunctionIrClassFactory] built classes, which may be incomplete otherwise.
// 2. Modifies stdlib global initializers. // 2. Modifies stdlib global initializers.
// 3. Defines runtime-declared globals. // 3. Defines runtime-declared globals.
require(context.producedLlvmModuleContainsStdlib) require(context.shouldDefineFunctionClasses)
} }
fun generate() { fun generate() {
@@ -582,6 +582,8 @@ internal class ObjCExportCodeGenerator(
unreachable() unreachable()
} }
LLVMSetLinkage(imp, LLVMLinkage.LLVMInternalLinkage)
val methods = selectorsToDefine.map { (selector, bridge) -> val methods = selectorsToDefine.map { (selector, bridge) ->
ObjCDataGenerator.Method(selector, getEncoding(bridge), constPointer(imp)) ObjCDataGenerator.Method(selector, getEncoding(bridge), constPointer(imp))
} }
@@ -858,7 +860,7 @@ private val ObjCExportBlockCodeGenerator.mappedFunctionNClasses get() =
.filter { it.descriptor.isMappedFunctionClass() } .filter { it.descriptor.isMappedFunctionClass() }
private fun ObjCExportBlockCodeGenerator.emitFunctionConverters() { private fun ObjCExportBlockCodeGenerator.emitFunctionConverters() {
require(context.producedLlvmModuleContainsStdlib) require(context.shouldDefineFunctionClasses)
mappedFunctionNClasses.forEach { functionClass -> mappedFunctionNClasses.forEach { functionClass ->
val convertToRetained = kotlinFunctionToRetainedBlockConverter(BlockPointerBridge(functionClass.arity, returnsVoid = false)) val convertToRetained = kotlinFunctionToRetainedBlockConverter(BlockPointerBridge(functionClass.arity, returnsVoid = false))
@@ -868,7 +870,7 @@ private fun ObjCExportBlockCodeGenerator.emitFunctionConverters() {
} }
private fun ObjCExportBlockCodeGenerator.emitBlockToKotlinFunctionConverters() { private fun ObjCExportBlockCodeGenerator.emitBlockToKotlinFunctionConverters() {
require(context.producedLlvmModuleContainsStdlib) require(context.shouldDefineFunctionClasses)
val functionClassesByArity = mappedFunctionNClasses.associateBy { it.arity } val functionClassesByArity = mappedFunctionNClasses.associateBy { it.arity }
val arityLimit = (functionClassesByArity.keys.maxOrNull() ?: -1) + 1 val arityLimit = (functionClassesByArity.keys.maxOrNull() ?: -1) + 1
@@ -0,0 +1,103 @@
/*
* 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.backend.konan.lower
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.DECLARATION_ORIGIN_FUNCTION_CLASS
import org.jetbrains.kotlin.backend.konan.serialization.KonanIrLinker
import org.jetbrains.kotlin.backend.konan.serialization.KonanManglerIr
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
import org.jetbrains.kotlin.ir.expressions.IrPropertyReference
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
internal class CacheInfoBuilder(private val context: Context, private val moduleDeserializer: KonanIrLinker.KonanPartialModuleDeserializer) {
fun build() = with(moduleDeserializer) {
moduleFragment.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitClass(declaration: IrClass) {
declaration.acceptChildrenVoid(this)
if (!declaration.isInterface && !declaration.isLocal
&& declaration.isExported && declaration.origin != DECLARATION_ORIGIN_FUNCTION_CLASS
) {
context.classFields.add(buildClassFields(declaration, context.getLayoutBuilder(declaration).getDeclaredFields()))
}
}
override fun visitFunction(declaration: IrFunction) {
// Don't need to visit the children here: classes would be all local and wouldn't be handled anyway,
// as for functions - both their callees will be handled and inline bodies will be built for the top function.
if (!declaration.isFakeOverride && declaration.isInline && declaration.isExported) {
context.inlineFunctionBodies.add(buildInlineFunctionReference(declaration))
trackCallees(declaration)
}
}
})
}
private val IrDeclaration.isExported
get() = with(KonanManglerIr) { isExported(compatibleMode = moduleDeserializer.compatibilityMode.oldSignatures) }
private val visitedInlineFunctions = mutableSetOf<IrFunction>()
private fun trackCallees(irFunction: IrFunction) {
if (irFunction in visitedInlineFunctions) return
visitedInlineFunctions += irFunction
irFunction.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
private fun processFunction(function: IrFunction) {
if (function.getPackageFragment() !is IrExternalPackageFragment) {
context.calledFromExportedInlineFunctions.add(function)
(function as? IrConstructor)?.constructedClass?.let { context.constructedFromExportedInlineFunctions.add(it) }
if (function.isInline && !function.isExported) {
// An exported inline function calls a non-exported inline function:
// should track its callees as well as it won't be handled by the main visitor.
trackCallees(function)
}
}
}
override fun visitCall(expression: IrCall) {
expression.acceptChildrenVoid(this)
processFunction(expression.symbol.owner)
}
override fun visitConstructorCall(expression: IrConstructorCall) {
expression.acceptChildrenVoid(this)
processFunction(expression.symbol.owner)
}
override fun visitFunctionReference(expression: IrFunctionReference) {
expression.acceptChildrenVoid(this)
processFunction(expression.symbol.owner)
}
override fun visitPropertyReference(expression: IrPropertyReference) {
expression.acceptChildrenVoid(this)
expression.getter?.owner?.let { processFunction(it) }
expression.setter?.owner?.let { processFunction(it) }
}
})
}
}
@@ -69,9 +69,10 @@ private abstract class BaseInteropIrTransformer(private val context: Context) :
val uniqueModuleName = irFile.packageFragmentDescriptor.module.name.asString() val uniqueModuleName = irFile.packageFragmentDescriptor.module.name.asString()
.let { it.substring(1, it.lastIndex) } .let { it.substring(1, it.lastIndex) }
val uniqueFileName = irFile.fileEntry.name
val uniquePrefix = buildString { val uniquePrefix = buildString {
append('_') append('_')
uniqueModuleName.toByteArray().joinTo(this, "") { (uniqueModuleName + uniqueFileName).toByteArray().joinTo(this, "") {
(0xFF and it.toInt()).toString(16).padStart(2, '0') (0xFF and it.toInt()).toString(16).padStart(2, '0')
} }
append('_') append('_')
@@ -7,20 +7,16 @@ package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.DeclarationTransformer import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.lower.* import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.common.lower.inline.DefaultInlineFunctionResolver import org.jetbrains.kotlin.backend.common.lower.inline.*
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesExtractionFromInlineFunctionsLowering
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineFunctionsLowering
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineLambdasLowering
import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.InlineFunctionOriginInfo import org.jetbrains.kotlin.backend.konan.InlineFunctionOriginInfo
import org.jetbrains.kotlin.ir.declarations.IrExternalPackageFragment import org.jetbrains.kotlin.ir.declarations.IrExternalPackageFragment
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.dump import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.file import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.util.getPackageFragment
// TODO: This is a bit hacky. Think about adopting persistent IR ideas. // TODO: This is a bit hacky. Think about adopting persistent IR ideas.
internal class NativeInlineFunctionResolver(override val context: Context) : DefaultInlineFunctionResolver(context) { internal class NativeInlineFunctionResolver(override val context: Context) : DefaultInlineFunctionResolver(context) {
@@ -40,6 +36,9 @@ internal class NativeInlineFunctionResolver(override val context: Context) : Def
val moduleDescriptor = packageFragment.packageFragmentDescriptor.containingDeclaration val moduleDescriptor = packageFragment.packageFragmentDescriptor.containingDeclaration
val moduleDeserializer = context.irLinker.moduleDeserializers[moduleDescriptor] val moduleDeserializer = context.irLinker.moduleDeserializers[moduleDescriptor]
?: error("No module deserializer for ${function.render()}") ?: error("No module deserializer for ${function.render()}")
require(context.config.cachedLibraries.isLibraryCached(moduleDeserializer.klib)) {
"No IR and no cache for ${function.render()}"
}
context.specialDeclarationsFactory.loweredInlineFunctions[function] = moduleDeserializer.deserializeInlineFunction(function) context.specialDeclarationsFactory.loweredInlineFunctions[function] = moduleDeserializer.deserializeInlineFunction(function)
function function
} }
@@ -60,7 +59,7 @@ internal class NativeInlineFunctionResolver(override val context: Context) : Def
LocalClassesInInlineLambdasLowering(context).lower(body, notLoweredFunction) LocalClassesInInlineLambdasLowering(context).lower(body, notLoweredFunction)
if (context.llvmModuleSpecification.containsPackageFragment(packageFragment)) { if (packageFragment !is IrExternalPackageFragment) {
// Do not extract local classes off of inline functions from cached libraries. // Do not extract local classes off of inline functions from cached libraries.
LocalClassesInInlineFunctionsLowering(context).lower(body, notLoweredFunction) LocalClassesInInlineFunctionsLowering(context).lower(body, notLoweredFunction)
LocalClassesExtractionFromInlineFunctionsLowering(context).lower(body, notLoweredFunction) LocalClassesExtractionFromInlineFunctionsLowering(context).lower(body, notLoweredFunction)
@@ -79,7 +79,7 @@ internal class ObjCExport(val context: Context, symbolTable: SymbolTable) {
internal fun generate(codegen: CodeGenerator) { internal fun generate(codegen: CodeGenerator) {
if (!target.family.isAppleFamily) return if (!target.family.isAppleFamily) return
if (context.producedLlvmModuleContainsStdlib) { if (context.shouldDefineFunctionClasses) {
ObjCExportBlockCodeGenerator(codegen).generate() ObjCExportBlockCodeGenerator(codegen).generate()
} }
@@ -122,8 +122,7 @@ internal object InlineFunctionBodyReferenceSerializer {
return stream.buf return stream.buf
} }
fun deserialize(data: ByteArray): List<SerializedInlineFunctionReference> { fun deserializeTo(data: ByteArray, result: MutableList<SerializedInlineFunctionReference>) {
val result = mutableListOf<SerializedInlineFunctionReference>()
val stream = ByteArrayStream(data) val stream = ByteArrayStream(data)
while (stream.hasData()) { while (stream.hasData()) {
val file = stream.readInt() val file = stream.readInt()
@@ -144,7 +143,6 @@ internal object InlineFunctionBodyReferenceSerializer {
result.add(SerializedInlineFunctionReference(file, functionSignature, body, startOffset, endOffset, result.add(SerializedInlineFunctionReference(file, functionSignature, body, startOffset, endOffset,
extensionReceiverSig, dispatchReceiverSig, outerReceiverSigs, valueParameterSigs, typeParameterSigs, defaultValues)) extensionReceiverSig, dispatchReceiverSig, outerReceiverSigs, valueParameterSigs, typeParameterSigs, defaultValues))
} }
return result
} }
} }
@@ -180,8 +178,7 @@ internal object ClassFieldsSerializer {
return stream.buf return stream.buf
} }
fun deserialize(data: ByteArray): List<SerializedClassFields> { fun deserializeTo(data: ByteArray, result: MutableList<SerializedClassFields>) {
val result = mutableListOf<SerializedClassFields>()
val stream = ByteArrayStream(data) val stream = ByteArrayStream(data)
while (stream.hasData()) { while (stream.hasData()) {
val file = stream.readInt() val file = stream.readInt()
@@ -199,7 +196,6 @@ internal object ClassFieldsSerializer {
} }
result.add(SerializedClassFields(file, classSignature, typeParameterSigs, outerThisIndex, fields)) result.add(SerializedClassFields(file, classSignature, typeParameterSigs, outerThisIndex, fields))
} }
return result
} }
} }
@@ -345,6 +341,7 @@ internal class KonanIrLinker(
FakeOverrideBuilder(this, symbolTable, KonanManglerIr, IrTypeSystemContextImpl(builtIns), friendModules, KonanFakeOverrideClassFilter) FakeOverrideBuilder(this, symbolTable, KonanManglerIr, IrTypeSystemContextImpl(builtIns), friendModules, KonanFakeOverrideClassFilter)
val moduleDeserializers = mutableMapOf<ModuleDescriptor, KonanPartialModuleDeserializer>() val moduleDeserializers = mutableMapOf<ModuleDescriptor, KonanPartialModuleDeserializer>()
val klibToModuleDeserializerMap = mutableMapOf<KotlinLibrary, KonanPartialModuleDeserializer>()
override fun createModuleDeserializer(moduleDescriptor: ModuleDescriptor, klib: KotlinLibrary?, strategyResolver: (String) -> DeserializationStrategy) = override fun createModuleDeserializer(moduleDescriptor: ModuleDescriptor, klib: KotlinLibrary?, strategyResolver: (String) -> DeserializationStrategy) =
when { when {
@@ -365,6 +362,7 @@ internal class KonanIrLinker(
} }
KonanPartialModuleDeserializer(moduleDescriptor, klib, strategyResolver, deserializationStrategy).also { KonanPartialModuleDeserializer(moduleDescriptor, klib, strategyResolver, deserializationStrategy).also {
moduleDeserializers[moduleDescriptor] = it moduleDeserializers[moduleDescriptor] = it
klibToModuleDeserializerMap[klib] = it
} }
} }
} }
@@ -383,6 +381,22 @@ internal class KonanIrLinker(
?: error("Unknown external package fragment: ${packageFragment.packageFragmentDescriptor}") ?: error("Unknown external package fragment: ${packageFragment.packageFragmentDescriptor}")
} }
private tailrec fun IdSignature.fileSignature(): IdSignature.FileSignature? = when (this) {
is IdSignature.FileSignature -> this
is IdSignature.CompositeSignature -> this.container.fileSignature()
else -> null
}
fun getExternalDeclarationFileName(declaration: IrDeclaration) = with(declaration) {
val externalPackageFragment = getPackageFragment() as? IrExternalPackageFragment
?: error("Expected an external package fragment for ${render()}")
val moduleDescriptor = externalPackageFragment.packageFragmentDescriptor.containingDeclaration
val moduleDeserializer = moduleDeserializers[moduleDescriptor]
?: error("No module deserializer for $moduleDescriptor")
val idSig = moduleDeserializer.descriptorSignatures[descriptor] ?: error("No signature for $descriptor")
idSig.topLevelSignature().fileSignature()?.fileName ?: error("No file for $idSig")
}
private val IrClass.firstNonClassParent: IrDeclarationParent private val IrClass.firstNonClassParent: IrDeclarationParent
get() { get() {
var parent = parent var parent = parent
@@ -414,7 +428,8 @@ internal class KonanIrLinker(
if (cacheDeserializationStrategy.contains(fileName)) if (cacheDeserializationStrategy.contains(fileName))
strategyResolver(fileName) strategyResolver(fileName)
else DeserializationStrategy.ON_DEMAND else DeserializationStrategy.ON_DEMAND
}, klib.versions.abiVersion ?: KotlinAbiVersion.CURRENT, containsErrorCode) { }, klib.versions.abiVersion ?: KotlinAbiVersion.CURRENT, containsErrorCode
) {
override val moduleFragment: IrModuleFragment = KonanIrModuleFragmentImpl(moduleDescriptor, builtIns) override val moduleFragment: IrModuleFragment = KonanIrModuleFragmentImpl(moduleDescriptor, builtIns)
fun buildInlineFunctionReference(irFunction: IrFunction): SerializedInlineFunctionReference { fun buildInlineFunctionReference(irFunction: IrFunction): SerializedInlineFunctionReference {
@@ -616,7 +631,8 @@ internal class KonanIrLinker(
override fun deserializedSymbolNotFound(idSig: IdSignature): Nothing = error("No descriptor found for $idSig") override fun deserializedSymbolNotFound(idSig: IdSignature): Nothing = error("No descriptor found for $idSig")
private val inlineFunctionReferences by lazy { private val inlineFunctionReferences by lazy {
cachedLibraries.getLibraryCache(klib)!!.serializedInlineFunctionBodies.associateBy { (cachedLibraries.getLibraryCache(klib)
?: error("No cache for ${klib.libraryName}")).serializedInlineFunctionBodies.associateBy {
fileDeserializationStates[it.file].declarationDeserializer.symbolDeserializer.deserializeIdSignature(it.functionSignature) fileDeserializationStates[it.file].declarationDeserializer.symbolDeserializer.deserializeIdSignature(it.functionSignature)
} }
} }
@@ -704,12 +720,15 @@ internal class KonanIrLinker(
} }
private val classesFields by lazy { private val classesFields by lazy {
cachedLibraries.getLibraryCache(klib)!!.serializedClassFields.associateBy { (cachedLibraries.getLibraryCache(klib)
?: error("No cache for ${klib.libraryName}")).serializedClassFields.associateBy {
fileDeserializationStates[it.file].declarationDeserializer.symbolDeserializer.deserializeIdSignature(it.classSignature) fileDeserializationStates[it.file].declarationDeserializer.symbolDeserializer.deserializeIdSignature(it.classSignature)
} }
} }
fun deserializeClassFields(irClass: IrClass, outerThisField: IrField?): List<ClassLayoutBuilder.FieldInfo> { fun deserializeClassFields(irClass: IrClass, outerThisField: IrField?): List<ClassLayoutBuilder.FieldInfo> {
irClass.getPackageFragment() as? IrExternalPackageFragment
?: error("Expected an external package fragment for ${irClass.render()}")
val signature = irClass.symbol.signature val signature = irClass.symbol.signature
?: error("No signature for ${irClass.render()}") ?: error("No signature for ${irClass.render()}")
val serializedClassFields = classesFields[signature] val serializedClassFields = classesFields[signature]
@@ -730,6 +749,9 @@ internal class KonanIrLinker(
referenceIrSymbol(symbolDeserializer, sigIndex, parameter.symbol) referenceIrSymbol(symbolDeserializer, sigIndex, parameter.symbol)
} }
} }
require(endToEndTypeParameterIndex == serializedClassFields.typeParameterSigs.size) {
"Not all type parameters have been referenced"
}
fun getByClassId(classId: ClassId): IrClassSymbol { fun getByClassId(classId: ClassId): IrClassSymbol {
val classIdSig = getPublicSignature(classId.packageFqName, classId.relativeClassName.asString()) val classIdSig = getPublicSignature(classId.packageFqName, classId.relativeClassName.asString())
@@ -764,6 +786,12 @@ internal class KonanIrLinker(
} }
} }
} }
val sortedFileIds by lazy {
fileDeserializationStates
.sortedBy { it.file.fileEntry.name }
.map { CacheSupport.cacheFileId(it.file.fqName.asString(), it.file.fileEntry.name) }
}
} }
private inner class KonanInteropModuleDeserializer( private inner class KonanInteropModuleDeserializer(
@@ -299,7 +299,7 @@ private fun getLibraryCacheDir(
): File { ): File {
val cacheBaseName = CachedLibraries.getCachedLibraryName(libraryName) val cacheBaseName = CachedLibraries.getCachedLibraryName(libraryName)
val cacheOutputKind = CompilerOutputKind.valueOf(cacheKind.uppercase()) val cacheOutputKind = CompilerOutputKind.valueOf(cacheKind.uppercase())
return OutputFiles(cacheDirectory.child(cacheBaseName).absolutePath, target, cacheOutputKind).mainFile.File() return OutputFiles(cacheDirectory.child(cacheBaseName).absolutePath, target, cacheOutputKind, producePerFileCache = false).mainFile
} }
private fun buildCache( private fun buildCache(