[K/N][IR][codegen][caches] Speed-up of per-file caches

This commit is contained in:
Igor Chevdar
2022-07-01 08:30:24 +03:00
committed by Space
parent aba8d8a859
commit 77f24a22dd
22 changed files with 339 additions and 148 deletions
@@ -219,11 +219,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
description = "Path to file to cache",
delimiter = ""
)
var fileToCache: String? = null
var filesToCache: Array<String>? = null
@Argument(value = "-Xmake-per-file-cache", description = "Force compiler to produce per-file cache")
var makePerFileCache: Boolean = false
@Argument(value = "-Xbatched-per-file-cache-build", valueDescription = "{disable|enable}", description = "Build per-file cache in one batch")
var batchedPerFileCacheBuild: String? = null
@Argument(value = "-Xexport-kdoc", description = "Export KDoc in framework header")
var exportKDoc: Boolean = false
@@ -285,11 +285,20 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
libraryToAddToCache?.let { put(LIBRARY_TO_ADD_TO_CACHE, it) }
put(CACHE_DIRECTORIES, cacheDirectories)
put(CACHED_LIBRARIES, parseCachedLibraries(arguments, configuration))
val fileToCache = arguments.fileToCache
if (outputKind == CompilerOutputKind.PRELIMINARY_CACHE && fileToCache == null)
val filesToCache = arguments.filesToCache
if (outputKind == CompilerOutputKind.PRELIMINARY_CACHE && filesToCache.isNullOrEmpty())
configuration.report(ERROR, "preliminary_cache only supported for per-file caches")
fileToCache?.let { put(FILE_TO_CACHE, it) }
filesToCache?.let { put(FILES_TO_CACHE, it.toList()) }
put(MAKE_PER_FILE_CACHE, arguments.makePerFileCache)
putIfNotNull(BATCHED_PER_FILE_CACHE_BUILD, when (arguments.batchedPerFileCacheBuild) {
null -> null
"enable" -> true
"disable" -> false
else -> {
configuration.report(ERROR, "Expected 'enable' or 'disable' for batchedPerFileCacheBuild")
false
}
})
parseShortModuleName(arguments, configuration, outputKind)?.let {
put(SHORT_MODULE_NAME, it)
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.descriptors.findPackage
import org.jetbrains.kotlin.builtins.FunctionInterfacePackageFragment
import org.jetbrains.kotlin.builtins.functions.FunctionClassDescriptor
import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import org.jetbrains.kotlin.descriptors.*
@@ -33,6 +34,8 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
internal object DECLARATION_ORIGIN_FUNCTION_CLASS : IrDeclarationOriginImpl("DECLARATION_ORIGIN_FUNCTION_CLASS")
val IrPackageFragment.isFunctionInterfaceFile get() = packageFragmentDescriptor is FunctionInterfacePackageFragment
abstract class KonanIrAbstractDescriptorBasedFunctionFactory : IrProvider, IrAbstractDescriptorBasedFunctionFactory()
internal class LazyIrFunctionFactory(
@@ -34,17 +34,25 @@ sealed class CacheDeserializationStrategy {
override fun contains(fqName: FqName, fileName: String) = true
}
class SingleFile(val filePath: String) : CacheDeserializationStrategy() {
class SingleFile(val filePath: String, val fqName: 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
}
class MultipleFiles(filePaths: List<String>, fqNames: List<String>) : CacheDeserializationStrategy() {
private val filePaths = filePaths.toSet()
private val fqNamesWithNames = fqNames.mapIndexed { i: Int, fqName: String -> Pair(fqName, File(filePaths[i]).name) }.toSet()
override fun contains(filePath: String) = filePath in filePaths
override fun contains(fqName: FqName, fileName: String) = Pair(fqName.asString(), fileName) in fqNamesWithNames
}
}
class PartialCacheInfo(val klib: KotlinLibrary, val strategy: CacheDeserializationStrategy)
class PartialCacheInfo(val klib: KotlinLibrary, var strategy: CacheDeserializationStrategy)
class CacheSupport(
private val configuration: CompilerConfiguration,
@@ -64,7 +72,7 @@ class CacheSupport(
?: configuration.reportCompilationError("cache directory $it is not found or not a directory")
}
internal fun tryGetImplicitOutput(): String? {
internal fun tryGetImplicitOutput(explicitlyProducePerFileCache: Boolean): String? {
val libraryToCache = libraryToCache ?: return null
// Put the resulting library in the first cache directory.
val cacheDirectory = implicitCacheDirectories.firstOrNull() ?: return null
@@ -75,19 +83,13 @@ class CacheSupport(
else
CachedLibraries.getPerFileCachedLibraryName(libraryToCache.klib)
)
if (singleFileStrategy == null)
return baseLibraryCacheDirectory.absolutePath
val singleFilePath = singleFileStrategy?.filePath
?: 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 fqName = singleFileStrategy.fqName
val fileCacheDirectory = baseLibraryCacheDirectory.child(cacheFileId(fqName, singleFilePath))
if (!explicitlyProducePerFileCache)
return fileCacheDirectory.absolutePath
val contentDirName = if (produce == CompilerOutputKind.PRELIMINARY_CACHE)
CachedLibraries.PER_FILE_CACHE_IR_LEVEL_DIR_NAME
else CachedLibraries.PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME
@@ -132,8 +134,26 @@ class CacheSupport(
if (libraryCache != null && libraryCache.granularity == CachedLibraries.Granularity.MODULE)
null
else {
val fileToCache = configuration.get(KonanConfigKeys.FILE_TO_CACHE)
PartialCacheInfo(libraryToAddToCache, if (fileToCache == null) CacheDeserializationStrategy.WholeModule else CacheDeserializationStrategy.SingleFile(fileToCache))
val filesToCache = configuration.get(KonanConfigKeys.FILES_TO_CACHE)
val strategy = if (filesToCache.isNullOrEmpty())
CacheDeserializationStrategy.WholeModule
else {
val fileProtos = Array<ProtoFile>(libraryToAddToCache.fileCount()) {
ProtoFile.parseFrom(libraryToAddToCache.file(it).codedInputStream, ExtensionRegistryLite.newInstance())
}
val fileNameToIndex = fileProtos.withIndex().associate { it.value.fileEntry.name to it.index }
val fqNames = filesToCache.map { fileName ->
val index = fileNameToIndex[fileName] ?: error("No file with path $fileName is found in klib ${libraryToAddToCache.libraryName}")
val fileReader = IrLibraryFileFromBytes(IrKlibBytesSource(libraryToAddToCache, index))
fileReader.deserializeFqName(fileProtos[index].fqNameList)
}
if (configuration.get(KonanConfigKeys.BATCHED_PER_FILE_CACHE_BUILD) == false)
CacheDeserializationStrategy.SingleFile(filesToCache.single(), fqNames.single())
else
CacheDeserializationStrategy.MultipleFiles(filesToCache, fqNames)
}
PartialCacheInfo(libraryToAddToCache, strategy)
}
}
@@ -90,22 +90,25 @@ class CachedLibraries(
private val cacheDirsContents = mutableMapOf<String, Set<String>>()
private fun selectCache(library: KotlinLibrary, cacheDir: File): Cache? {
val cacheBinaryPartDir = cacheDir.child(PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME)
// See Linker.renameOutput why is it ok to have an empty cache directory.
val cacheDirContents = cacheDirsContents.getOrPut(cacheDir.absolutePath) {
cacheBinaryPartDir.listFilesOrEmpty.map { it.absolutePath }.toSet()
cacheDir.listFilesOrEmpty.map { it.absolutePath }.toSet()
}
if (cacheDirContents.isEmpty()) return null
val cacheBinaryPartDir = cacheDir.child(PER_FILE_CACHE_BINARY_LEVEL_DIR_NAME)
val cacheBinaryPartDirContents = cacheDirsContents.getOrPut(cacheBinaryPartDir.absolutePath) {
cacheBinaryPartDir.listFilesOrEmpty.map { it.absolutePath }.toSet()
}
val baseName = getCachedLibraryName(library)
val dynamicFile = cacheBinaryPartDir.child(getArtifactName(baseName, CompilerOutputKind.DYNAMIC_CACHE))
val staticFile = cacheBinaryPartDir.child(getArtifactName(baseName, CompilerOutputKind.STATIC_CACHE))
if (dynamicFile.absolutePath in cacheDirContents && staticFile.absolutePath in cacheDirContents)
if (dynamicFile.absolutePath in cacheBinaryPartDirContents && staticFile.absolutePath in cacheBinaryPartDirContents)
error("Both dynamic and static caches files cannot be in the same directory." +
" Library: ${library.libraryName}, path to cache: ${cacheDir.absolutePath}")
return when {
dynamicFile.absolutePath in cacheDirContents -> Cache(Kind.DYNAMIC, Granularity.MODULE, dynamicFile.absolutePath)
staticFile.absolutePath in cacheDirContents -> Cache(Kind.STATIC, Granularity.MODULE, staticFile.absolutePath)
dynamicFile.absolutePath in cacheBinaryPartDirContents -> Cache(Kind.DYNAMIC, Granularity.MODULE, dynamicFile.absolutePath)
staticFile.absolutePath in cacheBinaryPartDirContents -> Cache(Kind.STATIC, Granularity.MODULE, staticFile.absolutePath)
else -> Cache(Kind.STATIC, Granularity.FILE, cacheDir.absolutePath)
}
}
@@ -70,6 +70,9 @@ val CompilerOutputKind.isCache: Boolean
get() = this == CompilerOutputKind.STATIC_CACHE || this == CompilerOutputKind.DYNAMIC_CACHE
|| this == CompilerOutputKind.PRELIMINARY_CACHE
val KonanConfig.involvesCodegen: Boolean
get() = produce != CompilerOutputKind.LIBRARY && produce != CompilerOutputKind.PRELIMINARY_CACHE && !omitFrameworkBinary
internal fun llvmIrDumpCallback(state: ActionState, module: IrModuleFragment, context: Context) {
module.let{}
if (state.beforeOrAfter == BeforeOrAfter.AFTER && state.phase.name in context.configuration.getList(KonanConfigKeys.SAVE_LLVM_IR)) {
@@ -143,7 +143,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
lazyValues[member] = newValue
}
private val localClassNames = mutableMapOf<IrAttributeContainer, String>()
val localClassNames = mutableMapOf<IrAttributeContainer, String>()
fun getLocalClassName(container: IrAttributeContainer): String? = localClassNames[container.attributeOwnerId]
@@ -227,22 +227,32 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
var llvmModule: LLVMModuleRef? = null
set(module) {
if (field != null) {
throw Error("Another LLVMModule in the context.")
}
field = module!!
llvm = Llvm(this, module)
debugInfo = DebugInfo(this)
}
lateinit var runtime: Runtime
private var runtimeDisposed = false
fun disposeRuntime() {
if (runtimeDisposed) return
if (::runtime.isInitialized) {
LLVMDisposeTargetData(runtime.targetData)
LLVMDisposeModule(runtime.llvmModule)
}
runtimeDisposed = true
}
lateinit var llvmImports: LlvmImports
lateinit var llvm: Llvm
val llvmImports: LlvmImports = Llvm.ImportsImpl(this)
lateinit var llvmDeclarations: LlvmDeclarations
lateinit var bitcodeFileName: String
lateinit var library: KonanLibraryLayout
private var llvmDisposed = false
var llvmDisposed = false
fun disposeLlvm() {
if (llvmDisposed) return
@@ -250,15 +260,10 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
LLVMDisposeDIBuilder(debugInfo.builder)
if (llvmModule != null)
LLVMDisposeModule(llvmModule)
if (::llvm.isInitialized) {
LLVMDisposeTargetData(llvm.runtime.targetData)
LLVMDisposeModule(llvm.runtime.llvmModule)
}
tryDisposeLLVMContext()
llvmDisposed = true
}
val cStubsManager = CStubsManager(config.target)
var cStubsManager = CStubsManager(config.target)
val coverage = CoverageManager(this)
@@ -354,7 +359,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
val llvmModuleSpecification: LlvmModuleSpecification by lazy {
when {
config.produce.isCache ->
CacheLlvmModuleSpecification(config.cachedLibraries, config.libraryToCache!!.klib)
CacheLlvmModuleSpecification(this, config.cachedLibraries, config.libraryToCache!!)
else -> DefaultLlvmModuleSpecification(config.cachedLibraries)
}
}
@@ -370,6 +375,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
val calledFromExportedInlineFunctions = mutableSetOf<IrFunction>()
val constructedFromExportedInlineFunctions = mutableSetOf<IrClass>()
val enumEntriesMaps = mutableMapOf<IrClass, Map<Name, LoweredEnumEntryDescription>>()
val targetAbiInfo: TargetAbiInfo by lazy {
when {
config.target == KonanTarget.MINGW_X64 -> {
@@ -410,13 +410,28 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
internal val libraryToCache: PartialCacheInfo?
get() = cacheSupport.libraryToCache
internal val producePerFileCache = libraryToCache?.strategy is CacheDeserializationStrategy.SingleFile
internal val producePerFileCache
get() = libraryToCache?.strategy is CacheDeserializationStrategy.SingleFile
val outputFiles =
OutputFiles(configuration.get(KonanConfigKeys.OUTPUT) ?: cacheSupport.tryGetImplicitOutput(),
target, produce, producePerFileCache)
internal val produceBatchedPerFileCache
get() = configuration.get(KonanConfigKeys.MAKE_PER_FILE_CACHE) == true
&& configuration.get(KonanConfigKeys.BATCHED_PER_FILE_CACHE_BUILD) != false
val tempFiles = TempFiles(outputFiles.outputName, configuration.get(KonanConfigKeys.TEMPORARY_FILES_DIR))
lateinit var outputFiles: OutputFiles
lateinit var tempFiles: TempFiles
init {
recreateOutputFiles(producePerFileCache)
}
fun recreateOutputFiles(explicitlyProducePerFileCache: Boolean) {
val outputPath = configuration.get(KonanConfigKeys.OUTPUT) ?: cacheSupport.tryGetImplicitOutput(explicitlyProducePerFileCache)
outputFiles = OutputFiles(outputPath, target, produce, explicitlyProducePerFileCache)
tempFiles = TempFiles(outputFiles.outputName, configuration.get(KonanConfigKeys.TEMPORARY_FILES_DIR))
println("ZZZ: ${outputFiles.outputName}")
}
val outputFile get() = outputFiles.mainFileName
@@ -44,10 +44,12 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create<List<String>>("paths to directories containing caches")
val CACHED_LIBRARIES: CompilerConfigurationKey<Map<String, String>>
= CompilerConfigurationKey.create<Map<String, String>>("mapping from library paths to cache paths")
val FILE_TO_CACHE: CompilerConfigurationKey<String?>
= CompilerConfigurationKey.create<String?>("which file should be compiled to cache")
val FILES_TO_CACHE: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("which files should be compiled to cache")
val MAKE_PER_FILE_CACHE: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create<Boolean>("make per-file cache")
val BATCHED_PER_FILE_CACHE_BUILD: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create<Boolean>("build per-file caches in batches")
val FRAMEWORK_IMPORT_HEADERS: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create<List<String>>("headers imported to framework header")
val FRIEND_MODULES: CompilerConfigurationKey<List<String>>
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.common.phaser.CompilerPhase
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.backend.common.serialization.codedInputStream
import org.jetbrains.kotlin.backend.common.serialization.proto.IrFile
import org.jetbrains.kotlin.backend.konan.llvm.tryDisposeLLVMContext
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
@@ -29,7 +30,7 @@ class KonanDriver(val project: Project, val environment: KotlinCoreEnvironment,
fun run() {
val fileNames = configuration.get(KonanConfigKeys.LIBRARY_TO_ADD_TO_CACHE)?.let { libPath ->
if (configuration.get(KonanConfigKeys.MAKE_PER_FILE_CACHE) != true)
null
configuration.get(KonanConfigKeys.FILES_TO_CACHE)
else {
val lib = createKonanLibrary(File(libPath), "default", null, true)
(0 until lib.fileCount()).map { fileIndex ->
@@ -42,8 +43,14 @@ class KonanDriver(val project: Project, val environment: KotlinCoreEnvironment,
if (fileNames == null) {
KonanConfig(project, configuration).runTopLevelPhases()
} else {
fileNames.forEach { buildFileCache(it, CompilerOutputKind.PRELIMINARY_CACHE) }
fileNames.forEach { buildFileCache(it, configuration.get(KonanConfigKeys.PRODUCE)!!) }
configuration.put(KonanConfigKeys.MAKE_PER_FILE_CACHE, true)
if (configuration.get(KonanConfigKeys.BATCHED_PER_FILE_CACHE_BUILD) == false) {
fileNames.forEach { buildFileCache(it, CompilerOutputKind.PRELIMINARY_CACHE) }
fileNames.forEach { buildFileCache(it, configuration.get(KonanConfigKeys.PRODUCE)!!) }
} else {
configuration.put(KonanConfigKeys.FILES_TO_CACHE, fileNames)
KonanConfig(project, configuration).runTopLevelPhases()
}
}
}
@@ -51,8 +58,7 @@ class KonanDriver(val project: Project, val environment: KotlinCoreEnvironment,
val phaseConfig = configuration.get(CLIConfigurationKeys.PHASE_CONFIG)!!
val subConfiguration = configuration.copy()
subConfiguration.put(KonanConfigKeys.PRODUCE, cacheKind)
subConfiguration.put(KonanConfigKeys.FILE_TO_CACHE, fileName)
subConfiguration.put(KonanConfigKeys.MAKE_PER_FILE_CACHE, false)
subConfiguration.put(KonanConfigKeys.FILES_TO_CACHE, listOf(fileName))
subConfiguration.put(CLIConfigurationKeys.PHASE_CONFIG, phaseConfig.toBuilder().build())
KonanConfig(project, subConfiguration).runTopLevelPhases()
}
@@ -102,6 +108,8 @@ private fun runTopLevelPhases(konanConfig: KonanConfig, environment: KotlinCoreE
toplevelPhase.cast<CompilerPhase<Context, Unit, Unit>>().invokeToplevel(context.phaseConfig, context, Unit)
} finally {
context.disposeLlvm()
context.disposeRuntime()
tryDisposeLLVMContext()
}
}
}
@@ -82,8 +82,8 @@ internal fun makeKonanModuleOpPhase(
actions = modulePhaseActions
)
internal val specialBackendChecksPhase = konanUnitPhase(
op = { irModule!!.files.forEach { SpecialBackendChecksTraversal(this).lower(it) } },
internal val specialBackendChecksPhase = makeKonanModuleLoweringPhase(
::SpecialBackendChecksTraversal,
name = "SpecialBackendChecks",
description = "Special backend checks"
)
@@ -151,6 +151,18 @@ internal val inventNamesForLocalClasses = makeKonanFileLoweringPhase(
internal val extractLocalClassesFromInlineBodies = makeKonanFileOpPhase(
{ context, irFile ->
irFile.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFunction(declaration: IrFunction) {
if (declaration.isInline)
context.inlineFunctionsSupport.saveNonLoweredInlineFunction(declaration)
declaration.acceptChildrenVoid(this)
}
})
LocalClassesInInlineLambdasLowering(context).lower(irFile)
LocalClassesInInlineFunctionsLowering(context).lower(irFile)
LocalClassesExtractionFromInlineFunctionsLowering(context).lower(irFile)
@@ -168,18 +180,6 @@ internal val wrapInlineDeclarationsWithReifiedTypeParametersLowering = makeKonan
internal val inlinePhase = makeKonanFileOpPhase(
{ context, irFile ->
irFile.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFunction(declaration: IrFunction) {
if (declaration.isInline)
context.inlineFunctionsSupport.getNonLoweredInlineFunction(declaration)
declaration.acceptChildrenVoid(this)
}
})
FunctionInlining(context, NativeInlineFunctionResolver(context)).lower(irFile)
},
name = "Inline",
@@ -286,12 +286,10 @@ internal val rangeContainsLoweringPhase = makeKonanFileLoweringPhase(
description = "Optimizes calls to contains() for ClosedRanges"
)
internal val functionsWithoutBoundCheck = makeKonanModuleOpPhase(
internal val functionsWithoutBoundCheck = konanUnitPhase(
name = "FunctionsWithoutBoundCheckGenerator",
description = "Functions without bounds check generation",
op = { context, _ ->
FunctionsWithoutBoundCheckGenerator(context).generate()
}
op = { FunctionsWithoutBoundCheckGenerator(this).generate() }
)
internal val forLoopsPhase = makeKonanFileOpPhase(
@@ -51,7 +51,7 @@ internal class CacheStorage(val context: Context) {
context.config.outputFiles.prepareTempDirectories()
if (!isPreliminaryCache)
saveCacheBitcodeDependencies()
if (isPreliminaryCache || context.configuration.get(KonanConfigKeys.FILE_TO_CACHE) == null) {
if (isPreliminaryCache || !context.config.producePerFileCache || context.config.produceBatchedPerFileCache) {
saveInlineFunctionBodies()
saveClassFields()
}
@@ -6,8 +6,10 @@
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.konan.ir.konanLibrary
import org.jetbrains.kotlin.builtins.FunctionInterfacePackageFragment
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.fileOrNull
import org.jetbrains.kotlin.ir.util.getPackageFragment
import org.jetbrains.kotlin.library.KotlinLibrary
@@ -41,10 +43,19 @@ internal class DefaultLlvmModuleSpecification(cachedLibraries: CachedLibraries)
}
internal class CacheLlvmModuleSpecification(
private val context: Context,
cachedLibraries: CachedLibraries,
private val libraryToCache: KotlinLibrary
private val libraryToCache: PartialCacheInfo
) : LlvmModuleSpecificationBase(cachedLibraries) {
override val isFinal = false
override fun containsLibrary(library: KotlinLibrary): Boolean = library == libraryToCache
override fun containsLibrary(library: KotlinLibrary): Boolean = library == libraryToCache.klib
override fun containsDeclaration(declaration: IrDeclaration): Boolean {
if (context.shouldDefineFunctionClasses && declaration.getPackageFragment().isFunctionInterfaceFile)
return true
if (!super.containsDeclaration(declaration)) return false
return (libraryToCache.strategy as? CacheDeserializationStrategy.SingleFile)
?.filePath.let { it == null || it == declaration.fileOrNull?.path }
}
}
@@ -49,7 +49,7 @@ class OutputFiles(outputPath: String?, target: KonanTarget, val produce: Compile
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 perFileCacheFileName = File(pathToPerFileCache ?: outputName).absoluteFile.name
val cacheFileName = File((pathToPerFileCache ?: outputName).fullOutputName()).absoluteFile.name
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.backend.konan.lower.ExpectToActualDefaultValueCopier
import org.jetbrains.kotlin.backend.konan.lower.SamSuperTypesChecker
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
import org.jetbrains.kotlin.backend.konan.serialization.*
import org.jetbrains.kotlin.builtins.FunctionInterfacePackageFragment
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.ir.declarations.*
@@ -67,7 +68,7 @@ internal fun fileValidationCallback(state: ActionState, irFile: IrFile, context:
internal fun konanUnitPhase(
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
prerequisite: Set<NamedCompilerPhase<Context, *>> = emptySet(),
op: Context.() -> Unit
) = namedOpUnitPhase(name, description, prerequisite, op)
@@ -328,6 +329,49 @@ internal val dependenciesLowerPhase = NamedCompilerPhase(
}
})
internal val umbrellaCompilation = NamedCompilerPhase(
name = "UmbrellaCompilation",
description = "A batched compilation with shared FE and ME phases",
prerequisite = emptySet(),
lower = object : CompilerPhase<Context, Unit, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Unit>, context: Context, input: Unit) {
val module = context.irModules.values.single()
val files = module.files.toList()
module.files.clear()
val functionInterfaceFiles = files.filter { it.isFunctionInterfaceFile }
for (file in files) {
if (file.isFunctionInterfaceFile) continue
// Pretend we're about to create a single file cache.
context.config.cacheSupport.libraryToCache!!.strategy =
CacheDeserializationStrategy.SingleFile(file.path, file.fqName.asString())
context.config.recreateOutputFiles(explicitlyProducePerFileCache = false)
module.files += file
if (context.shouldDefineFunctionClasses)
module.files += functionInterfaceFiles
entireBackend.invoke(phaseConfig, phaserState, context, Unit)
module.files.clear()
context.irModule!!.files.clear() // [dependenciesLowerPhase] puts all files to [context.irModule] for codegen.
context.inlineFunctionBodies.clear()
context.classFields.clear()
context.calledFromExportedInlineFunctions.clear()
context.constructedFromExportedInlineFunctions.clear()
context.localClassNames.clear()
context.testCasesToDump.clear()
context.mapping.loweredInlineFunctions.clear()
context.cStubsManager = CStubsManager(context.config.target)
}
module.files += files
}
})
internal val dumpTestsPhase = makeCustomPhase<Context, IrModuleFragment>(
name = "dumpTestsPhase",
description = "Dump the list of all available tests",
@@ -408,12 +452,10 @@ private val bitcodePostprocessingPhase = NamedCompilerPhase(
rewriteExternalCallsCheckerGlobals
)
private val backendCodegen = namedUnitPhase(
private val backendCodegen = NamedCompilerPhase(
name = "Backend codegen",
description = "Backend code generation",
lower = takeFromContext<Context, Unit, IrModuleFragment> { it.irModule!! } then
entryPointPhase then
functionsWithoutBoundCheck then
lower = entryPointPhase then
allLoweringsPhase then // Lower current module first.
dependenciesLowerPhase then // Then lower all libraries in topological order.
// With that we guarantee that inline functions are unlowered while being inlined.
@@ -422,36 +464,53 @@ private val backendCodegen = namedUnitPhase(
verifyBitcodePhase then
printBitcodePhase then
linkBitcodeDependenciesPhase then
bitcodePostprocessingPhase then
unitSink()
bitcodePostprocessingPhase
)
private val entireBackend = NamedCompilerPhase(
name = "EntireBackend",
description = "Entire backend",
lower = createLLVMImportsPhase then
buildAdditionalCacheInfoPhase then
takeFromContext { it.irModule!! } then
specialBackendChecksPhase then
backendCodegen then
unitSink() then
saveAdditionalCacheInfoPhase then
produceOutputPhase then
disposeLLVMPhase then
objectFilesPhase then
linkerPhase then
finalizeCachePhase
)
private val middleEnd = NamedCompilerPhase(
name = "MiddleEnd",
description = "Build and prepare IR for back end",
lower = createSymbolTablePhase then
objCExportPhase then
buildCExportsPhase then
psiToIrPhase then
destroySymbolTablePhase then
copyDefaultValuesToActualPhase then
checkSamSuperTypesPhase then
serializerPhase then
functionsWithoutBoundCheck
)
private val singleCompilation = NamedCompilerPhase(
name = "SingleCompilation",
description = "Single compilation",
lower = entireBackend
)
// Have to hide Context as type parameter in order to expose toplevelPhase outside of this module.
val toplevelPhase: CompilerPhase<*, Unit, Unit> = namedUnitPhase(
name = "Compiler",
description = "The whole compilation process",
lower = createSymbolTablePhase then
objCExportPhase then
buildCExportsPhase then
psiToIrPhase then
buildAdditionalCacheInfoPhase then
destroySymbolTablePhase then
copyDefaultValuesToActualPhase then
checkSamSuperTypesPhase then
serializerPhase then
specialBackendChecksPhase then
namedUnitPhase(
name = "Backend",
description = "All backend",
lower = backendCodegen then
produceOutputPhase then
disposeLLVMPhase then
unitSink()
) then
saveAdditionalCacheInfoPhase then
objectFilesPhase then
linkerPhase then
finalizeCachePhase
lower = middleEnd then
singleCompilation then
umbrellaCompilation
)
internal fun PhaseConfig.disableIf(phase: AnyNamedPhase, condition: Boolean) {
@@ -472,6 +531,9 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
disable(localEscapeAnalysisPhase)
disableIf(singleCompilation, config.produceBatchedPerFileCache)
disableUnless(umbrellaCompilation, config.produceBatchedPerFileCache)
// Don't serialize anything to a final executable.
disableUnless(serializerPhase, config.produce == CompilerOutputKind.LIBRARY)
disableUnless(entryPointPhase, config.produce == CompilerOutputKind.PROGRAM)
@@ -480,7 +542,7 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
disableUnless(finalizeCachePhase, config.produce.isCache)
disableUnless(exportInternalAbiPhase, config.produce.isCache)
disableUnless(buildCExportsPhase, config.produce.isNativeLibrary)
disableIf(backendCodegen, config.produce == CompilerOutputKind.LIBRARY || config.omitFrameworkBinary || config.produce == CompilerOutputKind.PRELIMINARY_CACHE)
disableUnless(backendCodegen, config.involvesCodegen)
disableUnless(checkExternalCallsPhase, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS))
disableUnless(rewriteExternalCallsCheckerGlobals, getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS))
disableUnless(stringConcatenationTypeNarrowingPhase, config.optimizationsEnabled)
@@ -405,9 +405,18 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
* All fields of the class instance.
* The order respects the class hierarchy, i.e. a class [fields] contains superclass [fields] as a prefix.
*/
val fields: List<FieldInfo> by lazy {
val fields: List<FieldInfo>
get() = fieldsInternal.map { fieldInfo ->
val mappedField = fieldInfo.irField?.let { context.mapping.lateInitFieldToNullableField[it] ?: it }
if (mappedField == fieldInfo.irField)
fieldInfo
else
mappedField!!.toFieldInfo().also { it.index = fieldInfo.index }
}
private val fieldsInternal: List<FieldInfo> by lazy {
val superClass = irClass.getSuperClassNotAny()
val superFields = if (superClass != null) context.getLayoutBuilder(superClass).fields else emptyList()
val superFields = if (superClass != null) context.getLayoutBuilder(superClass).fieldsInternal else emptyList()
val declaredFields = getDeclaredFields()
val sortedDeclaredFields = if (irClass.hasAnnotation(KonanFqNames.noReorderFields))
@@ -21,10 +21,7 @@ import org.jetbrains.kotlin.backend.konan.optimizations.*
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isFunction
import org.jetbrains.kotlin.ir.util.isReal
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.konan.target.Architecture
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -39,7 +36,12 @@ internal val contextLLVMSetupPhase = makeKonanModuleOpPhase(
// (see Llvm class in ContextUtils)
// Which in turn is determined by the clang flags
// used to compile runtime.bc.
// TODO
llvmContext = LLVMContextCreate()!!
context.llvmDisposed = false
context.runtime = Runtime(context.config.distribution.compilerInterface(context.config.target))
val llvmModule = LLVMModuleCreateWithNameInContext("out", llvmContext)!!
context.llvmModule = llvmModule
context.debugInfo.builder = LLVMCreateDIBuilder(llvmModule)
@@ -72,12 +74,24 @@ internal val createLLVMDeclarationsPhase = makeKonanModuleOpPhase(
}
)
internal val createLLVMImportsPhase = namedUnitPhase(
name = "CreateLLVMImoorts",
description = "Create LLVM imports",
lower = object : CompilerPhase<Context, Unit, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Unit>, context: Context, input: Unit) {
context.llvmImports = Llvm.ImportsImpl(context)
}
}
)
internal val disposeLLVMPhase = namedUnitPhase(
name = "DisposeLLVM",
description = "Dispose LLVM",
lower = object : CompilerPhase<Context, Unit, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Unit>, context: Context, input: Unit) {
context.disposeLlvm()
context.disposeRuntime()
tryDisposeLLVMContext()
}
}
)
@@ -444,8 +444,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) : Runti
private val target = context.config.target
val runtimeFile = context.config.distribution.compilerInterface(target)
override val runtime = Runtime(runtimeFile) // TODO: dispose
override val runtime get() = context.runtime
val targetTriple = runtime.target
@@ -2858,7 +2858,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
library == null -> context.config.moduleId.moduleConstructorName
library == context.config.libraryToCache?.klib
&& context.config.producePerFileCache ->
fileCtorName(library.uniqueName, context.config.outputFiles.perFileCacheFileName!!)
fileCtorName(library.uniqueName, context.config.outputFiles.perFileCacheFileName)
else -> library.moduleConstructorName
}
@@ -12,12 +12,8 @@ import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.ClassLayoutBuilder
import org.jetbrains.kotlin.backend.konan.descriptors.isTypedIntrinsic
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.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.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
@@ -22,8 +22,19 @@ import org.jetbrains.kotlin.name.Name
internal class InlineFunctionsSupport(mapping: NativeMapping) {
private val notLoweredInlineFunctions = mapping.notLoweredInlineFunctions
fun getNonLoweredInlineFunction(function: IrFunction) = notLoweredInlineFunctions.getOrPut(function.symbol) {
function.deepCopyWithVariables().also { it.patchDeclarationParents(function.parent) }
fun saveNonLoweredInlineFunction(function: IrFunction) {
getNonLoweredInlineFunction(function, copy = false)
}
fun getNonLoweredInlineFunction(function: IrFunction, copy: Boolean): IrFunction {
val notLoweredInlineFunction = notLoweredInlineFunctions.getOrPut(function.symbol) {
function.deepCopyWithVariables().also { it.patchDeclarationParents(function.parent) }
}
return if (copy)
notLoweredInlineFunction.deepCopyWithVariables().also { it.patchDeclarationParents(function.parent) }
else
notLoweredInlineFunction
}
}
@@ -35,11 +46,11 @@ internal class NativeInlineFunctionResolver(override val context: Context) : Def
context.mapping.loweredInlineFunctions[function]?.let { return it.irFunction }
val packageFragment = function.getPackageFragment()
val notLoweredFunction = if (packageFragment !is IrExternalPackageFragment) {
context.inlineFunctionsSupport.getNonLoweredInlineFunction(function).also {
val (possiblyLoweredFunction, shouldLower) = if (packageFragment !is IrExternalPackageFragment) {
context.inlineFunctionsSupport.getNonLoweredInlineFunction(function, copy = context.config.produceBatchedPerFileCache).also {
context.mapping.loweredInlineFunctions[function] =
InlineFunctionOriginInfo(it, packageFragment as IrFile, function.startOffset, function.endOffset)
}
} to true
} else {
// The function is from Lazy IR, get its body from the IR linker.
val moduleDescriptor = packageFragment.packageFragmentDescriptor.containingDeclaration
@@ -48,35 +59,38 @@ internal class NativeInlineFunctionResolver(override val context: Context) : Def
require(context.config.cachedLibraries.isLibraryCached(moduleDeserializer.klib)) {
"No IR and no cache for ${function.render()}"
}
context.mapping.loweredInlineFunctions[function] = moduleDeserializer.deserializeInlineFunction(function)
function
val (shouldLower, deserializedInlineFunction) = moduleDeserializer.deserializeInlineFunction(function)
context.mapping.loweredInlineFunctions[function] = deserializedInlineFunction
function to shouldLower
}
val body = notLoweredFunction.body ?: return notLoweredFunction
if (!shouldLower) return possiblyLoweredFunction
PreInlineLowering(context).lower(body, notLoweredFunction, context.mapping.loweredInlineFunctions[function]!!.irFile)
val body = possiblyLoweredFunction.body ?: return possiblyLoweredFunction
ArrayConstructorLowering(context).lower(body, notLoweredFunction)
PreInlineLowering(context).lower(body, possiblyLoweredFunction, context.mapping.loweredInlineFunctions[function]!!.irFile)
NullableFieldsForLateinitCreationLowering(context).lowerWithLocalDeclarations(notLoweredFunction)
NullableFieldsDeclarationLowering(context).lowerWithLocalDeclarations(notLoweredFunction)
LateinitUsageLowering(context).lower(body, notLoweredFunction)
ArrayConstructorLowering(context).lower(body, possiblyLoweredFunction)
SharedVariablesLowering(context).lower(body, notLoweredFunction)
NullableFieldsForLateinitCreationLowering(context).lowerWithLocalDeclarations(possiblyLoweredFunction)
NullableFieldsDeclarationLowering(context).lowerWithLocalDeclarations(possiblyLoweredFunction)
LateinitUsageLowering(context).lower(body, possiblyLoweredFunction)
OuterThisLowering(context).lower(notLoweredFunction)
SharedVariablesLowering(context).lower(body, possiblyLoweredFunction)
LocalClassesInInlineLambdasLowering(context).lower(body, notLoweredFunction)
OuterThisLowering(context).lower(possiblyLoweredFunction)
if (packageFragment !is IrExternalPackageFragment) {
LocalClassesInInlineLambdasLowering(context).lower(body, possiblyLoweredFunction)
if (context.llvmModuleSpecification.containsDeclaration(function)) {
// Do not extract local classes off of inline functions from cached libraries.
LocalClassesInInlineFunctionsLowering(context).lower(body, notLoweredFunction)
LocalClassesExtractionFromInlineFunctionsLowering(context).lower(body, notLoweredFunction)
LocalClassesInInlineFunctionsLowering(context).lower(body, possiblyLoweredFunction)
LocalClassesExtractionFromInlineFunctionsLowering(context).lower(body, possiblyLoweredFunction)
}
WrapInlineDeclarationsWithReifiedTypeParametersLowering(context).lower(body, notLoweredFunction)
WrapInlineDeclarationsWithReifiedTypeParametersLowering(context).lower(body, possiblyLoweredFunction)
return notLoweredFunction
return possiblyLoweredFunction
}
private fun DeclarationTransformer.lowerWithLocalDeclarations(function: IrFunction) {
@@ -307,6 +307,8 @@ object KonanFakeOverrideClassFilter : FakeOverrideClassFilter {
}
}
internal data class DeserializedInlineFunction(val firstAccess: Boolean, val function: InlineFunctionOriginInfo)
internal class KonanIrLinker(
private val currentModule: ModuleDescriptor,
override val translationPluginContext: TranslationPluginContext?,
@@ -387,14 +389,18 @@ internal class KonanIrLinker(
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")
fun getExternalDeclarationFileName(declaration: IrDeclaration) = when (val packageFragment = declaration.getPackageFragment()) {
is IrFile -> packageFragment.path
is IrExternalPackageFragment -> {
val moduleDescriptor = packageFragment.packageFragmentDescriptor.containingDeclaration
val moduleDeserializer = moduleDeserializers[moduleDescriptor] ?: error("No module deserializer for $moduleDescriptor")
val descriptor = declaration.descriptor
val idSig = moduleDeserializer.descriptorSignatures[descriptor] ?: error("No signature for $descriptor")
idSig.topLevelSignature().fileSignature()?.fileName ?: error("No file for $idSig")
}
else -> error("Unknown package fragment kind ${packageFragment::class.java}")
}
private val IrClass.firstNonClassParent: IrDeclarationParent
@@ -637,7 +643,16 @@ internal class KonanIrLinker(
}
}
fun deserializeInlineFunction(function: IrFunction): InlineFunctionOriginInfo {
private val deserializedInlineFunctions = mutableMapOf<IrFunction, InlineFunctionOriginInfo>()
fun deserializeInlineFunction(function: IrFunction): DeserializedInlineFunction {
deserializedInlineFunctions[function]?.let { return DeserializedInlineFunction(firstAccess = false, it) }
val result = deserializeInlineFunctionInternal(function)
deserializedInlineFunctions[function] = result
return DeserializedInlineFunction(firstAccess = true, result)
}
private fun deserializeInlineFunctionInternal(function: IrFunction): InlineFunctionOriginInfo {
val packageFragment = function.getPackageFragment() as? IrExternalPackageFragment
?: error("Expected an external package fragment for ${function.render()}")
if (function.parents.any { (it as? IrFunction)?.isInline == true }) {