Improve compilation to multiple LLVM modules/object files (#3334)

This commit is contained in:
SvyatoslavScherbina
2019-09-17 10:10:17 +03:00
committed by GitHub
parent a9164d858c
commit d4a98168c0
12 changed files with 101 additions and 34 deletions
@@ -289,13 +289,11 @@ private fun selectIncludes(
outputKind: CompilerOutputKind
): List<String> {
val includes = arguments.includes?.toList().orEmpty()
val produceBinaryOrBitcode = outputKind.let { it.isNativeBinary || it == CompilerOutputKind.BITCODE }
return if (includes.isNotEmpty() && !produceBinaryOrBitcode) {
return if (includes.isNotEmpty() && outputKind == CompilerOutputKind.LIBRARY) {
configuration.report(
ERROR,
"The $INCLUDE_ARG flag is only supported when producing native binaries or bitcode files, " +
"but the compiler is producing ${outputKind.name.toLowerCase()}"
"The $INCLUDE_ARG flag is not supported when producing ${outputKind.name.toLowerCase()}"
)
emptyList()
} else {
@@ -150,7 +150,7 @@ internal val Context.getUnboxFunction: (IrClass) -> IrSimpleFunction by Context.
* If output target is native binary then the cache is created.
*/
internal fun initializeCachedBoxes(context: Context) {
if (context.config.produce.isNativeBinary) {
if (context.producedLlvmModuleContainsStdlib) {
BoxCache.values().forEach { cache ->
val cacheName = "${cache.name}_CACHE"
val rangeStart = "${cache.name}_RANGE_FROM"
@@ -16,12 +16,28 @@ import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Family
val CompilerOutputKind.isNativeBinary: Boolean get() = when (this) {
/**
* Supposed to be true for a single LLVM module within final binary.
*/
val CompilerOutputKind.isFinalBinary: Boolean get() = when (this) {
CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC,
CompilerOutputKind.STATIC, CompilerOutputKind.FRAMEWORK -> true
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
}
val CompilerOutputKind.involvesBitcodeGeneration: Boolean
get() = this != CompilerOutputKind.LIBRARY
internal val Context.producedLlvmModuleContainsStdlib: Boolean
get() = this.llvmModuleSpecification.containsModule(this.stdlibModule)
val CompilerOutputKind.involvesLinkStage: Boolean
get() = when (this) {
CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC,
CompilerOutputKind.STATIC, CompilerOutputKind.FRAMEWORK -> true
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
}
internal fun produceCStubs(context: Context) {
val llvmModule = context.llvmModule!!
context.cStubsManager.compile(context.config.clang, context.messageCollector, context.inVerbosePhase)?.let {
@@ -29,9 +45,12 @@ internal fun produceCStubs(context: Context) {
}
}
private fun linkAllDependecies(context: Context, generatedBitcodeFiles: List<String>) {
val nativeLibraries = context.config.nativeLibraries + context.config.defaultNativeLibraries
private fun linkAllDependencies(context: Context, generatedBitcodeFiles: List<String>) {
val runtimeNativeLibraries = context.config.runtimeNativeLibraries
.takeIf { context.producedLlvmModuleContainsStdlib }.orEmpty()
val launcherNativeLibraries = context.config.launcherNativeLibraries
.takeIf { context.config.produce == CompilerOutputKind.PROGRAM }.orEmpty()
val nativeLibraries = context.config.nativeLibraries + runtimeNativeLibraries + launcherNativeLibraries
val bitcodeLibraries = context.llvm.bitcodeToLink.map { it.bitcodePaths }.flatten().filter { it.isBitcode }
val additionalBitcodeFilesToLink = context.llvm.additionalProducedBitcodeFiles
val bitcodeFiles = (nativeLibraries + generatedBitcodeFiles + additionalBitcodeFilesToLink + bitcodeLibraries).toSet()
@@ -89,7 +108,7 @@ internal fun produceOutput(context: Context) {
if (produce == CompilerOutputKind.FRAMEWORK && context.config.produceStaticFramework) {
embedAppleLinkerOptionsToBitcode(context.llvm, context.config)
}
linkAllDependecies(context, generatedBitcodeFiles)
linkAllDependencies(context, generatedBitcodeFiles)
runLlvmPipeline(context)
// Insert `_main` after pipeline so we won't worry about optimizations
// corrupting entry point.
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.backend.common.serialization.KotlinMangler
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
import org.jetbrains.kotlin.backend.konan.llvm.coverage.CoverageManager
import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl
import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
import org.jetbrains.kotlin.library.SerializedIrModule
@@ -460,6 +461,15 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
get() = this.builtIns.any.module
lateinit var compilerOutput: List<ObjectFile>
val llvmModuleSpecification: LlvmModuleSpecification = object : LlvmModuleSpecification {
// Currently all code is compiled to single LLVM module.
override fun importsKotlinDeclarationsFromOtherObjectFiles(): Boolean = false
override fun containsLibrary(library: KonanLibrary): Boolean = true
override fun containsModule(module: ModuleDescriptor): Boolean = true
override fun containsModule(module: IrModuleFragment): Boolean = true
override fun containsDeclaration(declaration: IrDeclaration): Boolean = true
}
}
private fun MemberScope.getContributedClassifier(name: String) =
@@ -151,16 +151,17 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
return resolvedLibraries.filterRoots { (!it.isDefault && !this.purgeUserLibs) || it.isNeededForLink }.getFullList(TopologicalLibraryOrder)
}
internal val defaultNativeLibraries: List<String> = mutableListOf<String>().apply {
internal val runtimeNativeLibraries: List<String> = mutableListOf<String>().apply {
add(if (debug) "debug.bc" else "release.bc")
add(if (memoryModel == MemoryModel.STRICT) "strict.bc" else "relaxed.bc")
if (produce == CompilerOutputKind.PROGRAM) {
addAll(distribution.launcherFiles)
}
}.map {
File(distribution.defaultNatives(target)).child(it).absolutePath
}
internal val launcherNativeLibraries: List<String> = distribution.launcherFiles.map {
File(distribution.defaultNatives(target)).child(it).absolutePath
}
internal val nativeLibraries: List<String> =
configuration.getList(KonanConfigKeys.NATIVE_LIBRARY_FILES)
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.konan.library.KonanLibrary
/**
* Defines what LLVM module should consist of.
*/
interface LlvmModuleSpecification {
fun importsKotlinDeclarationsFromOtherObjectFiles(): Boolean
fun containsLibrary(library: KonanLibrary): Boolean
fun containsModule(module: ModuleDescriptor): Boolean
fun containsModule(module: IrModuleFragment): Boolean
fun containsDeclaration(declaration: IrDeclaration): Boolean
}
@@ -419,7 +419,7 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
disableIf(dependenciesLowerPhase, config.produce == CompilerOutputKind.LIBRARY)
disableUnless(entryPointPhase, config.produce == CompilerOutputKind.PROGRAM)
disableIf(bitcodePhase, config.produce == CompilerOutputKind.LIBRARY)
disableUnless(linkPhase, config.produce.isNativeBinary)
disableUnless(linkPhase, config.produce.involvesLinkStage)
disableIf(testProcessorPhase, getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) == TestRunnerKind.NONE)
disableUnless(buildDFGPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(devirtualizationPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
@@ -150,7 +150,7 @@ internal interface ContextUtils : RuntimeAware {
* or just drop all [else] branches of corresponding conditionals.
*/
fun isExternal(declaration: IrDeclaration): Boolean {
return false
return !context.llvmModuleSpecification.containsDeclaration(declaration)
}
/**
@@ -399,6 +399,8 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
context.config.resolvedLibraries
.getFullList(TopologicalLibraryOrder)
.filter { (!it.isDefault && !context.config.purgeUserLibs) || imports.bitcodeIsUsed(it) }
// TODO: the filter above is incorrect when compiling to multiple LLVM modules.
.filter { context.llvmModuleSpecification.containsLibrary(it) }
}
val additionalProducedBitcodeFiles = mutableListOf<String>()
@@ -2246,7 +2246,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
private fun appendDebugSelector() {
if (!context.config.produce.isNativeBinary) return
if (!context.producedLlvmModuleContainsStdlib) return
val llvmDebugSelector =
context.llvm.staticData.placeGlobal("KonanNeedDebugInfo",
Int32(if (context.shouldContainDebugInfo()) 1 else 0))
@@ -2304,7 +2304,14 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
LLVMSetLinkage(ctorFunction, LLVMLinkage.LLVMExternalLinkage)
val initializers = libraryToInitializers.getValue(library)
appendStaticInitializers(ctorFunction, initializers)
if (library == null || context.llvmModuleSpecification.containsLibrary(library)) {
appendStaticInitializers(ctorFunction, initializers)
} else {
check(initializers.isEmpty()) {
"found initializer from ${library.libraryFile}, which is not included into compilation"
}
}
ctorFunction
}
@@ -2342,7 +2349,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
private fun appendGlobalCtors(ctorFunctions: List<LLVMValueRef>) {
if (context.config.produce.isNativeBinary) {
if (context.config.produce.isFinalBinary) {
// Generate function calling all [ctorFunctions].
val globalCtorFunction = LLVMAddFunction(context.llvmModule, "_Konan_constructors", kVoidFuncType)!!
LLVMSetLinkage(globalCtorFunction, LLVMLinkage.LLVMPrivateLinkage)
@@ -50,7 +50,7 @@ internal class CoverageManager(val context: Context) {
}
private fun checkRestrictions(): Boolean {
val isKindAllowed = with(context.config.produce) { isNativeBinary || this == CompilerOutputKind.BITCODE }
val isKindAllowed = context.config.produce.involvesBitcodeGeneration
val target = context.config.target
val isTargetAllowed = target == KonanTarget.MACOS_X64 || target == KonanTarget.IOS_X64
return isKindAllowed && isTargetAllowed
@@ -262,8 +262,8 @@ internal class ObjCExportCodeGenerator(
val sortedAdaptersPointer = staticData.placeGlobalConstArray("", type, sortedAdapters)
// Note: this globals replace runtime globals with weak linkage:
staticData.placeGlobal(prefix, sortedAdaptersPointer, isExported = true)
staticData.placeGlobal("${prefix}Num", Int32(sortedAdapters.size), isExported = true)
replaceExternalWeakOrCommonGlobal(prefix, sortedAdaptersPointer)
replaceExternalWeakOrCommonGlobal("${prefix}Num", Int32(sortedAdapters.size))
}
}
@@ -381,6 +381,16 @@ internal class ObjCExportCodeGenerator(
}
private fun ObjCExportCodeGenerator.replaceExternalWeakOrCommonGlobal(name: String, value: ConstValue) {
val global = staticData.placeGlobal(name, value, isExported = true)
if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherObjectFiles()) {
// Note: actually this is required only if global's weak/common definition is in other object file,
// but it is simpler to do this for all globals, considering that all usages can't be removed by DCE anyway.
context.llvm.usedGlobals += global.llvmGlobal
}
}
private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
irClass: IrClass,
converter: ConstPointer? = null,
@@ -400,20 +410,14 @@ private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
val writableTypeInfoType = runtime.writableTypeInfoType!!
val writableTypeInfoValue = Struct(writableTypeInfoType, objCExportAddition)
val global = if (codegen.isExternal(irClass)) {
if (codegen.isExternal(irClass)) {
// Note: this global replaces the external one with common linkage.
staticData.createGlobal(
writableTypeInfoType,
irClass.writableTypeInfoSymbolName,
isExported = true
)
replaceExternalWeakOrCommonGlobal(irClass.writableTypeInfoSymbolName, writableTypeInfoValue)
} else {
context.llvmDeclarations.forClass(irClass).writableTypeInfoGlobal!!.also {
it.setLinkage(LLVMLinkage.LLVMExternalLinkage)
}
}.setInitializer(writableTypeInfoValue)
}
global.setInitializer(writableTypeInfoValue)
}
private val ObjCExportCodeGenerator.kotlinToObjCFunctionType: LLVMTypeRef
@@ -480,7 +484,7 @@ private fun ObjCExportCodeGenerator.emitBlockToKotlinFunctionConverters() {
).pointer.getElementPtr(0)
// Note: this global replaces the weak global defined in runtime.
staticData.placeGlobal("Kotlin_ObjCExport_blockToFunctionConverters", ptr, isExported = true)
replaceExternalWeakOrCommonGlobal("Kotlin_ObjCExport_blockToFunctionConverters", ptr)
}
private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
@@ -42,7 +42,11 @@ internal class ObjCExport(val context: Context, symbolTable: SymbolTable) {
private fun produceInterface(): ObjCExportedInterface? {
if (!target.family.isAppleFamily) return null
if (!context.config.produce.isNativeBinary) return null // TODO: emit RTTI to the same modules as classes belong to.
if (!context.config.produce.isFinalBinary) return null
// TODO: emit RTTI to the same modules as classes belong to.
// Not possible yet, since ObjCExport translates the entire "world" API at once
// and can't do this per-module, e.g. due to global name conflict resolution.
val produceFramework = context.config.produce == CompilerOutputKind.FRAMEWORK
@@ -69,7 +73,7 @@ internal class ObjCExport(val context: Context, symbolTable: SymbolTable) {
internal fun generate(codegen: CodeGenerator) {
if (!target.family.isAppleFamily) return
if (!context.config.produce.isNativeBinary) return // TODO: emit RTTI to the same modules as classes belong to.
if (!context.config.produce.isFinalBinary) return // TODO: emit RTTI to the same modules as classes belong to.
val mapper = exportedInterface?.mapper ?: ObjCExportMapper()
val namer = exportedInterface?.namer ?: ObjCExportNamerImpl(