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 outputKind: CompilerOutputKind
): List<String> { ): List<String> {
val includes = arguments.includes?.toList().orEmpty() 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( configuration.report(
ERROR, ERROR,
"The $INCLUDE_ARG flag is only supported when producing native binaries or bitcode files, " + "The $INCLUDE_ARG flag is not supported when producing ${outputKind.name.toLowerCase()}"
"but the compiler is producing ${outputKind.name.toLowerCase()}"
) )
emptyList() emptyList()
} else { } else {
@@ -150,7 +150,7 @@ internal val Context.getUnboxFunction: (IrClass) -> IrSimpleFunction by Context.
* If output target is native binary then the cache is created. * If output target is native binary then the cache is created.
*/ */
internal fun initializeCachedBoxes(context: Context) { internal fun initializeCachedBoxes(context: Context) {
if (context.config.produce.isNativeBinary) { if (context.producedLlvmModuleContainsStdlib) {
BoxCache.values().forEach { cache -> BoxCache.values().forEach { cache ->
val cacheName = "${cache.name}_CACHE" val cacheName = "${cache.name}_CACHE"
val rangeStart = "${cache.name}_RANGE_FROM" 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.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Family 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.PROGRAM, CompilerOutputKind.DYNAMIC,
CompilerOutputKind.STATIC, CompilerOutputKind.FRAMEWORK -> true CompilerOutputKind.STATIC, CompilerOutputKind.FRAMEWORK -> true
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false 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) { internal fun produceCStubs(context: Context) {
val llvmModule = context.llvmModule!! val llvmModule = context.llvmModule!!
context.cStubsManager.compile(context.config.clang, context.messageCollector, context.inVerbosePhase)?.let { 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>) { private fun linkAllDependencies(context: Context, generatedBitcodeFiles: List<String>) {
val runtimeNativeLibraries = context.config.runtimeNativeLibraries
val nativeLibraries = context.config.nativeLibraries + context.config.defaultNativeLibraries .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 bitcodeLibraries = context.llvm.bitcodeToLink.map { it.bitcodePaths }.flatten().filter { it.isBitcode }
val additionalBitcodeFilesToLink = context.llvm.additionalProducedBitcodeFiles val additionalBitcodeFilesToLink = context.llvm.additionalProducedBitcodeFiles
val bitcodeFiles = (nativeLibraries + generatedBitcodeFiles + additionalBitcodeFilesToLink + bitcodeLibraries).toSet() val bitcodeFiles = (nativeLibraries + generatedBitcodeFiles + additionalBitcodeFilesToLink + bitcodeLibraries).toSet()
@@ -89,7 +108,7 @@ internal fun produceOutput(context: Context) {
if (produce == CompilerOutputKind.FRAMEWORK && context.config.produceStaticFramework) { if (produce == CompilerOutputKind.FRAMEWORK && context.config.produceStaticFramework) {
embedAppleLinkerOptionsToBitcode(context.llvm, context.config) embedAppleLinkerOptionsToBitcode(context.llvm, context.config)
} }
linkAllDependecies(context, generatedBitcodeFiles) linkAllDependencies(context, generatedBitcodeFiles)
runLlvmPipeline(context) runLlvmPipeline(context)
// Insert `_main` after pipeline so we won't worry about optimizations // Insert `_main` after pipeline so we won't worry about optimizations
// corrupting entry point. // 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.objcexport.ObjCExport
import org.jetbrains.kotlin.backend.konan.llvm.coverage.CoverageManager import org.jetbrains.kotlin.backend.konan.llvm.coverage.CoverageManager
import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl 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.name.FqName
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
import org.jetbrains.kotlin.library.SerializedIrModule import org.jetbrains.kotlin.library.SerializedIrModule
@@ -460,6 +461,15 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
get() = this.builtIns.any.module get() = this.builtIns.any.module
lateinit var compilerOutput: List<ObjectFile> 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) = 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) 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 (debug) "debug.bc" else "release.bc")
add(if (memoryModel == MemoryModel.STRICT) "strict.bc" else "relaxed.bc") add(if (memoryModel == MemoryModel.STRICT) "strict.bc" else "relaxed.bc")
if (produce == CompilerOutputKind.PROGRAM) {
addAll(distribution.launcherFiles)
}
}.map { }.map {
File(distribution.defaultNatives(target)).child(it).absolutePath 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> = internal val nativeLibraries: List<String> =
configuration.getList(KonanConfigKeys.NATIVE_LIBRARY_FILES) 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) disableIf(dependenciesLowerPhase, config.produce == CompilerOutputKind.LIBRARY)
disableUnless(entryPointPhase, config.produce == CompilerOutputKind.PROGRAM) disableUnless(entryPointPhase, config.produce == CompilerOutputKind.PROGRAM)
disableIf(bitcodePhase, config.produce == CompilerOutputKind.LIBRARY) 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) disableIf(testProcessorPhase, getNotNull(KonanConfigKeys.GENERATE_TEST_RUNNER) == TestRunnerKind.NONE)
disableUnless(buildDFGPhase, getBoolean(KonanConfigKeys.OPTIMIZATION)) disableUnless(buildDFGPhase, getBoolean(KonanConfigKeys.OPTIMIZATION))
disableUnless(devirtualizationPhase, 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. * or just drop all [else] branches of corresponding conditionals.
*/ */
fun isExternal(declaration: IrDeclaration): Boolean { 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 context.config.resolvedLibraries
.getFullList(TopologicalLibraryOrder) .getFullList(TopologicalLibraryOrder)
.filter { (!it.isDefault && !context.config.purgeUserLibs) || imports.bitcodeIsUsed(it) } .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>() val additionalProducedBitcodeFiles = mutableListOf<String>()
@@ -2246,7 +2246,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
} }
private fun appendDebugSelector() { private fun appendDebugSelector() {
if (!context.config.produce.isNativeBinary) return if (!context.producedLlvmModuleContainsStdlib) return
val llvmDebugSelector = val llvmDebugSelector =
context.llvm.staticData.placeGlobal("KonanNeedDebugInfo", context.llvm.staticData.placeGlobal("KonanNeedDebugInfo",
Int32(if (context.shouldContainDebugInfo()) 1 else 0)) 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) LLVMSetLinkage(ctorFunction, LLVMLinkage.LLVMExternalLinkage)
val initializers = libraryToInitializers.getValue(library) 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 ctorFunction
} }
@@ -2342,7 +2349,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
} }
private fun appendGlobalCtors(ctorFunctions: List<LLVMValueRef>) { private fun appendGlobalCtors(ctorFunctions: List<LLVMValueRef>) {
if (context.config.produce.isNativeBinary) { if (context.config.produce.isFinalBinary) {
// Generate function calling all [ctorFunctions]. // Generate function calling all [ctorFunctions].
val globalCtorFunction = LLVMAddFunction(context.llvmModule, "_Konan_constructors", kVoidFuncType)!! val globalCtorFunction = LLVMAddFunction(context.llvmModule, "_Konan_constructors", kVoidFuncType)!!
LLVMSetLinkage(globalCtorFunction, LLVMLinkage.LLVMPrivateLinkage) LLVMSetLinkage(globalCtorFunction, LLVMLinkage.LLVMPrivateLinkage)
@@ -50,7 +50,7 @@ internal class CoverageManager(val context: Context) {
} }
private fun checkRestrictions(): Boolean { 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 target = context.config.target
val isTargetAllowed = target == KonanTarget.MACOS_X64 || target == KonanTarget.IOS_X64 val isTargetAllowed = target == KonanTarget.MACOS_X64 || target == KonanTarget.IOS_X64
return isKindAllowed && isTargetAllowed return isKindAllowed && isTargetAllowed
@@ -262,8 +262,8 @@ internal class ObjCExportCodeGenerator(
val sortedAdaptersPointer = staticData.placeGlobalConstArray("", type, sortedAdapters) val sortedAdaptersPointer = staticData.placeGlobalConstArray("", type, sortedAdapters)
// Note: this globals replace runtime globals with weak linkage: // Note: this globals replace runtime globals with weak linkage:
staticData.placeGlobal(prefix, sortedAdaptersPointer, isExported = true) replaceExternalWeakOrCommonGlobal(prefix, sortedAdaptersPointer)
staticData.placeGlobal("${prefix}Num", Int32(sortedAdapters.size), isExported = true) 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( private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
irClass: IrClass, irClass: IrClass,
converter: ConstPointer? = null, converter: ConstPointer? = null,
@@ -400,20 +410,14 @@ private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
val writableTypeInfoType = runtime.writableTypeInfoType!! val writableTypeInfoType = runtime.writableTypeInfoType!!
val writableTypeInfoValue = Struct(writableTypeInfoType, objCExportAddition) 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. // Note: this global replaces the external one with common linkage.
staticData.createGlobal( replaceExternalWeakOrCommonGlobal(irClass.writableTypeInfoSymbolName, writableTypeInfoValue)
writableTypeInfoType,
irClass.writableTypeInfoSymbolName,
isExported = true
)
} else { } else {
context.llvmDeclarations.forClass(irClass).writableTypeInfoGlobal!!.also { context.llvmDeclarations.forClass(irClass).writableTypeInfoGlobal!!.also {
it.setLinkage(LLVMLinkage.LLVMExternalLinkage) it.setLinkage(LLVMLinkage.LLVMExternalLinkage)
} }.setInitializer(writableTypeInfoValue)
} }
global.setInitializer(writableTypeInfoValue)
} }
private val ObjCExportCodeGenerator.kotlinToObjCFunctionType: LLVMTypeRef private val ObjCExportCodeGenerator.kotlinToObjCFunctionType: LLVMTypeRef
@@ -480,7 +484,7 @@ private fun ObjCExportCodeGenerator.emitBlockToKotlinFunctionConverters() {
).pointer.getElementPtr(0) ).pointer.getElementPtr(0)
// Note: this global replaces the weak global defined in runtime. // 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() { private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
@@ -42,7 +42,11 @@ internal class ObjCExport(val context: Context, symbolTable: SymbolTable) {
private fun produceInterface(): ObjCExportedInterface? { private fun produceInterface(): ObjCExportedInterface? {
if (!target.family.isAppleFamily) return null 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 val produceFramework = context.config.produce == CompilerOutputKind.FRAMEWORK
@@ -69,7 +73,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.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 mapper = exportedInterface?.mapper ?: ObjCExportMapper()
val namer = exportedInterface?.namer ?: ObjCExportNamerImpl( val namer = exportedInterface?.namer ?: ObjCExportNamerImpl(