[K/N] Allow to optimize runtime bitcode separately
Since runtime is split into several LLVM modules there is no LTO optimizations between them during debug compilation because the latter performs almost no optimizations to preserve debug info. Of course, it affects runtime performance of debug binaries. To improve it, we can link and aggressively optimize runtime modules and then link optimized module into unoptimized Kotlin LLVM module. This commit also introduces `linkRuntime` binary option that forces the way we link runtime modules.
This commit is contained in:
+2
@@ -27,6 +27,8 @@ object BinaryOptions : BinaryOptionRegistry() {
|
||||
val unitSuspendFunctionObjCExport by option<UnitSuspendFunctionObjCExport>()
|
||||
|
||||
val gcSchedulerType by option<GCSchedulerType>()
|
||||
|
||||
val linkRuntime by option<RuntimeLinkageStrategyBinaryOption>()
|
||||
}
|
||||
|
||||
open class BinaryOption<T : Any>(
|
||||
|
||||
+11
-5
@@ -78,15 +78,15 @@ internal fun produceCStubs(context: Context) {
|
||||
private fun linkAllDependencies(context: Context, generatedBitcodeFiles: List<String>) {
|
||||
val config = context.config
|
||||
|
||||
val runtimeNativeLibraries = config.runtimeNativeLibraries
|
||||
.takeIf { context.producedLlvmModuleContainsStdlib }.orEmpty()
|
||||
// TODO: Possibly slow, maybe to a separate phase?
|
||||
val runtimeModules = RuntimeLinkageStrategy.pick(context).run()
|
||||
|
||||
val launcherNativeLibraries = config.launcherNativeLibraries
|
||||
.takeIf { config.produce == CompilerOutputKind.PROGRAM }.orEmpty()
|
||||
|
||||
linkObjC(context)
|
||||
|
||||
val nativeLibraries = config.nativeLibraries + runtimeNativeLibraries + launcherNativeLibraries
|
||||
val nativeLibraries = config.nativeLibraries + launcherNativeLibraries
|
||||
|
||||
val bitcodeLibraries = context.llvm.bitcodeToLink.map { it.bitcodePaths }.flatten().filter { it.isBitcode }
|
||||
val additionalBitcodeFilesToLink = context.llvm.additionalProducedBitcodeFiles
|
||||
@@ -96,6 +96,12 @@ private fun linkAllDependencies(context: Context, generatedBitcodeFiles: List<St
|
||||
bitcodeFiles += exceptionsSupportNativeLibrary
|
||||
|
||||
val llvmModule = context.llvmModule!!
|
||||
runtimeModules.forEach {
|
||||
val failed = llvmLinkModules2(context, llvmModule, it)
|
||||
if (failed != 0) {
|
||||
error("Failed to link ${it.getName()}")
|
||||
}
|
||||
}
|
||||
bitcodeFiles.forEach {
|
||||
parseAndLinkBitcodeFile(context, llvmModule, it)
|
||||
}
|
||||
@@ -205,14 +211,14 @@ internal fun produceOutput(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAndLinkBitcodeFile(context: Context, llvmModule: LLVMModuleRef, path: String) {
|
||||
internal fun parseAndLinkBitcodeFile(context: Context, llvmModule: LLVMModuleRef, path: String) {
|
||||
val parsedModule = parseBitcodeFile(path)
|
||||
if (!context.shouldUseDebugInfoFromNativeLibs()) {
|
||||
LLVMStripModuleDebugInfo(parsedModule)
|
||||
}
|
||||
val failed = llvmLinkModules2(context, llvmModule, parsedModule)
|
||||
if (failed != 0) {
|
||||
throw Error("failed to link $path") // TODO: retrieve error message from LLVM.
|
||||
throw Error("failed to link $path")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+215
-119
@@ -1,12 +1,11 @@
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import kotlinx.cinterop.alloc
|
||||
import kotlinx.cinterop.memScoped
|
||||
import kotlinx.cinterop.ptr
|
||||
import kotlinx.cinterop.value
|
||||
import kotlinx.cinterop.*
|
||||
import llvm.*
|
||||
import org.jetbrains.kotlin.backend.common.LoggingContext
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||
import org.jetbrains.kotlin.konan.target.*
|
||||
import java.io.Closeable
|
||||
|
||||
private fun initializeLlvmGlobalPassRegistry() {
|
||||
val passRegistry = LLVMGetGlobalPassRegistry()
|
||||
@@ -25,79 +24,244 @@ private fun initializeLlvmGlobalPassRegistry() {
|
||||
LLVMInitializeObjCARCOpts(passRegistry)
|
||||
}
|
||||
|
||||
enum class LlvmOptimizationLevel(val value: Int) {
|
||||
NONE(0),
|
||||
DEFAULT(1),
|
||||
AGGRESSIVE(3)
|
||||
}
|
||||
|
||||
private class LlvmPipelineConfiguration(context: Context) {
|
||||
enum class LlvmSizeLevel(val value: Int) {
|
||||
NONE(0),
|
||||
DEFAULT(1),
|
||||
AGGRESSIVE(2)
|
||||
}
|
||||
|
||||
private val target = context.config.target
|
||||
private val configurables: Configurables = context.config.platform.configurables
|
||||
/**
|
||||
* Incorporates everything that is used to tune a [LlvmOptimizationPipeline].
|
||||
*/
|
||||
data class LlvmPipelineConfig(
|
||||
val targetTriple: String,
|
||||
val cpuModel: String,
|
||||
val cpuFeatures: String,
|
||||
val optimizationLevel: LlvmOptimizationLevel,
|
||||
val sizeLevel: LlvmSizeLevel,
|
||||
val codegenOptimizationLevel: LLVMCodeGenOptLevel,
|
||||
val relocMode: LLVMRelocMode,
|
||||
val codeModel: LLVMCodeModel,
|
||||
val globalDce: Boolean,
|
||||
val internalize: Boolean,
|
||||
val makeDeclarationsHidden: Boolean,
|
||||
val objCPasses: Boolean,
|
||||
val inlineThreshold: Int?,
|
||||
)
|
||||
|
||||
val targetTriple: String = context.llvm.targetTriple
|
||||
|
||||
val cpuModel: String = configurables.targetCpu ?: run {
|
||||
private fun getCpuModel(context: Context): String {
|
||||
val target = context.config.target
|
||||
val configurables: Configurables = context.config.platform.configurables
|
||||
return configurables.targetCpu ?: run {
|
||||
context.reportCompilationWarning("targetCpu for target $target was not set. Targeting `generic` cpu.")
|
||||
"generic"
|
||||
}
|
||||
}
|
||||
|
||||
val cpuFeatures: String = configurables.targetCpuFeatures ?: ""
|
||||
private fun getCpuFeatures(context: Context): String =
|
||||
context.config.platform.configurables.targetCpuFeatures ?: ""
|
||||
|
||||
/**
|
||||
* Null value means that LLVM should use default inliner params
|
||||
* for the provided optimization and size level.
|
||||
*/
|
||||
val customInlineThreshold: Int? = when {
|
||||
context.shouldOptimize() -> configurables.llvmInlineThreshold?.let {
|
||||
it.toIntOrNull() ?: run {
|
||||
context.reportCompilationWarning(
|
||||
"`llvmInlineThreshold` should be an integer. Got `$it` instead. Using default value."
|
||||
)
|
||||
null
|
||||
}
|
||||
private fun tryGetInlineThreshold(context: Context): Int? {
|
||||
val configurables: Configurables = context.config.platform.configurables
|
||||
return configurables.llvmInlineThreshold?.let {
|
||||
it.toIntOrNull() ?: run {
|
||||
context.reportCompilationWarning(
|
||||
"`llvmInlineThreshold` should be an integer. Got `$it` instead. Using default value."
|
||||
)
|
||||
null
|
||||
}
|
||||
context.shouldContainDebugInfo() -> null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates [LlvmPipelineConfig] that is used for [RuntimeLinkageStrategy.LinkAndOptimize].
|
||||
* There is no DCE or internalization here because optimized module will be linked later.
|
||||
* Still, runtime is not intended to be debugged by user, and we can optimize it pretty aggressively
|
||||
* even in debug compilation.
|
||||
*/
|
||||
internal fun createLTOPipelineConfigForRuntime(context: Context): LlvmPipelineConfig {
|
||||
val configurables: Configurables = context.config.platform.configurables
|
||||
return LlvmPipelineConfig(
|
||||
context.llvm.targetTriple,
|
||||
getCpuModel(context),
|
||||
getCpuFeatures(context),
|
||||
LlvmOptimizationLevel.AGGRESSIVE,
|
||||
LlvmSizeLevel.NONE,
|
||||
LLVMCodeGenOptLevel.LLVMCodeGenLevelAggressive,
|
||||
configurables.currentRelocationMode(context).translateToLlvmRelocMode(),
|
||||
LLVMCodeModel.LLVMCodeModelDefault,
|
||||
globalDce = false,
|
||||
internalize = false,
|
||||
objCPasses = configurables is AppleConfigurables,
|
||||
makeDeclarationsHidden = false,
|
||||
inlineThreshold = tryGetInlineThreshold(context)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* In the end, Kotlin/Native generates a single LLVM module during compilation.
|
||||
* It won't be linked with any other LLVM module, so we can hide and DCE unused symbols.
|
||||
*
|
||||
* The set of optimizations relies on current compiler configuration.
|
||||
* In case of debug we do almost nothing (that's why we need [createLTOPipelineConfigForRuntime]),
|
||||
* but for release binaries we rely on "closed" world and enable a lot of optimizations.
|
||||
*/
|
||||
internal fun createLTOFinalPipelineConfig(context: Context): LlvmPipelineConfig {
|
||||
val target = context.config.target
|
||||
val configurables: Configurables = context.config.platform.configurables
|
||||
val cpuModel = getCpuModel(context)
|
||||
val cpuFeatures = getCpuFeatures(context)
|
||||
val optimizationLevel: LlvmOptimizationLevel = when {
|
||||
context.shouldOptimize() -> LlvmOptimizationLevel.AGGRESSIVE
|
||||
context.shouldContainDebugInfo() -> LlvmOptimizationLevel.NONE
|
||||
else -> LlvmOptimizationLevel.DEFAULT
|
||||
}
|
||||
|
||||
val sizeLevel: LlvmSizeLevel = when {
|
||||
// We try to optimize code as much as possible on embedded targets.
|
||||
target is KonanTarget.ZEPHYR ||
|
||||
target == KonanTarget.WASM32 -> LlvmSizeLevel.AGGRESSIVE
|
||||
target == KonanTarget.WASM32 -> LlvmSizeLevel.AGGRESSIVE
|
||||
context.shouldOptimize() -> LlvmSizeLevel.NONE
|
||||
context.shouldContainDebugInfo() -> LlvmSizeLevel.NONE
|
||||
else -> LlvmSizeLevel.NONE
|
||||
}
|
||||
|
||||
val codegenOptimizationLevel: LLVMCodeGenOptLevel = when {
|
||||
context.shouldOptimize() -> LLVMCodeGenOptLevel.LLVMCodeGenLevelAggressive
|
||||
context.shouldContainDebugInfo() -> LLVMCodeGenOptLevel.LLVMCodeGenLevelNone
|
||||
else -> LLVMCodeGenOptLevel.LLVMCodeGenLevelDefault
|
||||
}
|
||||
|
||||
val relocMode: LLVMRelocMode = configurables.currentRelocationMode(context).translateToLlvmRelocMode()
|
||||
|
||||
private fun RelocationModeFlags.Mode.translateToLlvmRelocMode() = when (this) {
|
||||
RelocationModeFlags.Mode.PIC -> LLVMRelocMode.LLVMRelocPIC
|
||||
RelocationModeFlags.Mode.STATIC -> LLVMRelocMode.LLVMRelocStatic
|
||||
RelocationModeFlags.Mode.DEFAULT -> LLVMRelocMode.LLVMRelocDefault
|
||||
}
|
||||
|
||||
val codeModel: LLVMCodeModel = LLVMCodeModel.LLVMCodeModelDefault
|
||||
val globalDce = true
|
||||
// Since we are in a "closed world" internalization can be safely used
|
||||
// to reduce size of a bitcode with global dce.
|
||||
val internalize = context.llvmModuleSpecification.isFinal
|
||||
// Hidden visibility makes symbols internal when linking the binary.
|
||||
// When producing dynamic library, this enables stripping unused symbols from binary with -dead_strip flag,
|
||||
// similar to DCE enabled by internalize but later:
|
||||
//
|
||||
// Important for binary size, workarounds references to undefined symbols from interop libraries.
|
||||
val makeDeclarationsHidden = context.config.produce == CompilerOutputKind.STATIC_CACHE
|
||||
val objcPasses = configurables is AppleConfigurables
|
||||
|
||||
enum class LlvmOptimizationLevel(val value: Int) {
|
||||
NONE(0),
|
||||
DEFAULT(1),
|
||||
AGGRESSIVE(3)
|
||||
// Null value means that LLVM should use default inliner params
|
||||
// for the provided optimization and size level.
|
||||
val inlineThreshold: Int? = when {
|
||||
context.shouldOptimize() -> tryGetInlineThreshold(context)
|
||||
context.shouldContainDebugInfo() -> null
|
||||
else -> null
|
||||
}
|
||||
|
||||
enum class LlvmSizeLevel(val value: Int) {
|
||||
NONE(0),
|
||||
DEFAULT(1),
|
||||
AGGRESSIVE(2)
|
||||
return LlvmPipelineConfig(
|
||||
context.llvm.targetTriple,
|
||||
cpuModel,
|
||||
cpuFeatures,
|
||||
optimizationLevel,
|
||||
sizeLevel,
|
||||
codegenOptimizationLevel,
|
||||
relocMode,
|
||||
codeModel,
|
||||
globalDce,
|
||||
internalize,
|
||||
makeDeclarationsHidden,
|
||||
objcPasses,
|
||||
inlineThreshold,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and executes LLVM LTO pipeline on the given [llvmModule].
|
||||
*
|
||||
* Note: this class is intentionally uncoupled from [Context].
|
||||
* Please avoid depending on it.
|
||||
*/
|
||||
class LlvmOptimizationPipeline(
|
||||
private val config: LlvmPipelineConfig,
|
||||
private val llvmModule: LLVMModuleRef,
|
||||
private val logger: LoggingContext? = null
|
||||
) : Closeable {
|
||||
private val arena = Arena()
|
||||
|
||||
private val targetMachine: LLVMTargetMachineRef by lazy {
|
||||
val target = arena.alloc<LLVMTargetRefVar>()
|
||||
val foundLlvmTarget = LLVMGetTargetFromTriple(config.targetTriple, target.ptr, null) == 0
|
||||
check(foundLlvmTarget) { "Cannot get target from triple ${config.targetTriple}." }
|
||||
LLVMCreateTargetMachine(
|
||||
target.value,
|
||||
config.targetTriple,
|
||||
config.cpuModel,
|
||||
config.cpuFeatures,
|
||||
config.codegenOptimizationLevel,
|
||||
config.relocMode,
|
||||
config.codeModel)!!
|
||||
}
|
||||
|
||||
private val modulePasses = LLVMCreatePassManager()
|
||||
private val passBuilder = LLVMPassManagerBuilderCreate()
|
||||
|
||||
private fun init() {
|
||||
initLLVMTargets()
|
||||
initializeLlvmGlobalPassRegistry()
|
||||
}
|
||||
|
||||
private fun populatePasses() {
|
||||
LLVMPassManagerBuilderSetOptLevel(passBuilder, config.optimizationLevel.value)
|
||||
LLVMPassManagerBuilderSetSizeLevel(passBuilder, config.sizeLevel.value)
|
||||
LLVMKotlinAddTargetLibraryInfoWrapperPass(modulePasses, config.targetTriple)
|
||||
// TargetTransformInfo pass.
|
||||
LLVMAddAnalysisPasses(targetMachine, modulePasses)
|
||||
if (config.internalize) {
|
||||
LLVMAddInternalizePass(modulePasses, 0)
|
||||
}
|
||||
if (config.makeDeclarationsHidden) {
|
||||
makeVisibilityHiddenLikeLlvmInternalizePass(llvmModule)
|
||||
}
|
||||
if (config.globalDce) {
|
||||
LLVMAddGlobalDCEPass(modulePasses)
|
||||
}
|
||||
config.inlineThreshold?.let { threshold ->
|
||||
LLVMPassManagerBuilderUseInlinerWithThreshold(passBuilder, threshold)
|
||||
}
|
||||
|
||||
// Pipeline that is similar to `llvm-lto`.
|
||||
LLVMPassManagerBuilderPopulateLTOPassManager(passBuilder, modulePasses, Internalize = 0, RunInliner = 1)
|
||||
|
||||
if (config.objCPasses) {
|
||||
// Lower ObjC ARC intrinsics (e.g. `@llvm.objc.clang.arc.use(...)`).
|
||||
// While Kotlin/Native codegen itself doesn't produce these intrinsics, they might come
|
||||
// from cinterop "glue" bitcode.
|
||||
// TODO: Consider adding other ObjC passes.
|
||||
LLVMAddObjCARCContractPass(modulePasses)
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
init()
|
||||
populatePasses()
|
||||
logger?.log {
|
||||
"""
|
||||
Running LLVM optimizations with the following parameters:
|
||||
target_triple: ${config.targetTriple}
|
||||
cpu_model: ${config.cpuModel}
|
||||
cpu_features: ${config.cpuFeatures}
|
||||
optimization_level: ${config.optimizationLevel.value}
|
||||
size_level: ${config.sizeLevel.value}
|
||||
inline_threshold: ${config.inlineThreshold ?: "default"}
|
||||
""".trimIndent()
|
||||
}
|
||||
LLVMRunPassManager(modulePasses, llvmModule)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
LLVMPassManagerBuilderDispose(passBuilder)
|
||||
LLVMDisposeTargetMachine(targetMachine)
|
||||
LLVMDisposePassManager(modulePasses)
|
||||
arena.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -118,83 +282,15 @@ private class LlvmPipelineConfiguration(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun runLlvmOptimizationPipeline(context: Context) {
|
||||
val llvmModule = context.llvmModule!!
|
||||
val config = LlvmPipelineConfiguration(context)
|
||||
context.log {
|
||||
"""
|
||||
Running LLVM optimizations with the following parameters:
|
||||
target_triple: ${config.targetTriple}
|
||||
cpu_model: ${config.cpuModel}
|
||||
cpu_features: ${config.cpuFeatures}
|
||||
optimization_level: ${config.optimizationLevel.value}
|
||||
size_level: ${config.sizeLevel.value}
|
||||
inline_threshold: ${config.customInlineThreshold ?: "default"}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
LlvmPipelineConfiguration.initLLVMTargets()
|
||||
|
||||
memScoped {
|
||||
initializeLlvmGlobalPassRegistry()
|
||||
val passBuilder = LLVMPassManagerBuilderCreate()
|
||||
val modulePasses = LLVMCreatePassManager()
|
||||
LLVMPassManagerBuilderSetOptLevel(passBuilder, config.optimizationLevel.value)
|
||||
LLVMPassManagerBuilderSetSizeLevel(passBuilder, config.sizeLevel.value)
|
||||
// TODO: use LLVMGetTargetFromName instead.
|
||||
val target = alloc<LLVMTargetRefVar>()
|
||||
val foundLlvmTarget = LLVMGetTargetFromTriple(config.targetTriple, target.ptr, null) == 0
|
||||
check(foundLlvmTarget) { "Cannot get target from triple ${config.targetTriple}." }
|
||||
|
||||
val targetMachine = LLVMCreateTargetMachine(
|
||||
target.value,
|
||||
config.targetTriple,
|
||||
config.cpuModel,
|
||||
config.cpuFeatures,
|
||||
config.codegenOptimizationLevel,
|
||||
config.relocMode,
|
||||
config.codeModel)
|
||||
|
||||
LLVMKotlinAddTargetLibraryInfoWrapperPass(modulePasses, config.targetTriple)
|
||||
// TargetTransformInfo pass.
|
||||
LLVMAddAnalysisPasses(targetMachine, modulePasses)
|
||||
if (context.llvmModuleSpecification.isFinal) {
|
||||
// Since we are in a "closed world" internalization can be safely used
|
||||
// to reduce size of a bitcode with global dce.
|
||||
LLVMAddInternalizePass(modulePasses, 0)
|
||||
} else if (context.config.produce == CompilerOutputKind.STATIC_CACHE) {
|
||||
// Hidden visibility makes symbols internal when linking the binary.
|
||||
// When producing dynamic library, this enables stripping unused symbols from binary with -dead_strip flag,
|
||||
// similar to DCE enabled by internalize but later:
|
||||
makeVisibilityHiddenLikeLlvmInternalizePass(llvmModule)
|
||||
// Important for binary size, workarounds references to undefined symbols from interop libraries.
|
||||
}
|
||||
LLVMAddGlobalDCEPass(modulePasses)
|
||||
|
||||
config.customInlineThreshold?.let { threshold ->
|
||||
LLVMPassManagerBuilderUseInlinerWithThreshold(passBuilder, threshold)
|
||||
}
|
||||
|
||||
// Pipeline that is similar to `llvm-lto`.
|
||||
LLVMPassManagerBuilderPopulateLTOPassManager(passBuilder, modulePasses, Internalize = 0, RunInliner = 1)
|
||||
|
||||
// Lower ObjC ARC intrinsics (e.g. `@llvm.objc.clang.arc.use(...)`).
|
||||
// While Kotlin/Native codegen itself doesn't produce these intrinsics, they might come
|
||||
// from cinterop "glue" bitcode.
|
||||
// TODO: Consider adding other ObjC passes.
|
||||
LLVMAddObjCARCContractPass(modulePasses)
|
||||
|
||||
LLVMRunPassManager(modulePasses, llvmModule)
|
||||
|
||||
LLVMPassManagerBuilderDispose(passBuilder)
|
||||
LLVMDisposeTargetMachine(targetMachine)
|
||||
LLVMDisposePassManager(modulePasses)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RelocationModeFlags.currentRelocationMode(context: Context): RelocationModeFlags.Mode =
|
||||
when (determineLinkerOutput(context)) {
|
||||
LinkerOutputKind.DYNAMIC_LIBRARY -> dynamicLibraryRelocationMode
|
||||
LinkerOutputKind.STATIC_LIBRARY -> staticLibraryRelocationMode
|
||||
LinkerOutputKind.EXECUTABLE -> executableRelocationMode
|
||||
}
|
||||
}
|
||||
|
||||
private fun RelocationModeFlags.Mode.translateToLlvmRelocMode() = when (this) {
|
||||
RelocationModeFlags.Mode.PIC -> LLVMRelocMode.LLVMRelocPIC
|
||||
RelocationModeFlags.Mode.STATIC -> LLVMRelocMode.LLVMRelocStatic
|
||||
RelocationModeFlags.Mode.DEFAULT -> LLVMRelocMode.LLVMRelocDefault
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import llvm.LLVMModuleCreateWithNameInContext
|
||||
import llvm.LLVMModuleRef
|
||||
import llvm.LLVMStripModuleDebugInfo
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.getName
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.llvmContext
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.llvmLinkModules2
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile
|
||||
|
||||
/**
|
||||
* To avoid combinatorial explosion, we split runtime into several LLVM modules.
|
||||
* This approach might cause performance degradation in some compilation modes because
|
||||
* there is no LTO between runtime modules.
|
||||
* RuntimeLinkageStrategy allows to choose the way we link runtime into final application or cache
|
||||
* and mitigate the problem above.
|
||||
*
|
||||
*/
|
||||
internal sealed class RuntimeLinkageStrategy {
|
||||
|
||||
abstract fun run(): List<LLVMModuleRef>
|
||||
|
||||
/**
|
||||
* Link runtime "as is", without any optimizations. Doable for "release" because LTO
|
||||
* in this mode is quite aggressive.
|
||||
*/
|
||||
class Raw(private val runtimeNativeLibraries: List<LLVMModuleRef>) : RuntimeLinkageStrategy() {
|
||||
|
||||
override fun run(): List<LLVMModuleRef> =
|
||||
runtimeNativeLibraries
|
||||
}
|
||||
|
||||
/**
|
||||
* Links all runtime modules into a single one and optimizes it.
|
||||
*/
|
||||
class LinkAndOptimize(
|
||||
private val context: Context,
|
||||
private val runtimeNativeLibraries: List<LLVMModuleRef>
|
||||
) : RuntimeLinkageStrategy() {
|
||||
|
||||
override fun run(): List<LLVMModuleRef> {
|
||||
if (runtimeNativeLibraries.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val runtimeModule = LLVMModuleCreateWithNameInContext("runtime", llvmContext)!!
|
||||
runtimeNativeLibraries.forEach {
|
||||
val failed = llvmLinkModules2(context, runtimeModule, it)
|
||||
if (failed != 0) {
|
||||
throw Error("Failed to link ${it.getName()}")
|
||||
}
|
||||
}
|
||||
val config = createLTOPipelineConfigForRuntime(context)
|
||||
LlvmOptimizationPipeline(config, runtimeModule, context).use {
|
||||
it.run()
|
||||
}
|
||||
return listOf(runtimeModule)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in cases when runtime is not linked directly, e.g. it is a part of stdlib cache.
|
||||
*/
|
||||
object None : RuntimeLinkageStrategy() {
|
||||
override fun run(): List<LLVMModuleRef> = emptyList()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Choose runtime linkage strategy based on current compiler configuration and [BinaryOptions.linkRuntime].
|
||||
*/
|
||||
internal fun pick(context: Context): RuntimeLinkageStrategy {
|
||||
val binaryOption = context.config.configuration.get(BinaryOptions.linkRuntime)
|
||||
val runtimeNativeLibraries = context.config.runtimeNativeLibraries
|
||||
.takeIf { context.producedLlvmModuleContainsStdlib }
|
||||
val runtimeLlvmModules = runtimeNativeLibraries?.map {
|
||||
val parsedModule = parseBitcodeFile(it)
|
||||
if (!context.shouldUseDebugInfoFromNativeLibs()) {
|
||||
LLVMStripModuleDebugInfo(parsedModule)
|
||||
}
|
||||
parsedModule
|
||||
}
|
||||
return when {
|
||||
runtimeLlvmModules == null -> return None
|
||||
binaryOption == RuntimeLinkageStrategyBinaryOption.Raw -> Raw(runtimeLlvmModules)
|
||||
binaryOption == RuntimeLinkageStrategyBinaryOption.Optimize -> LinkAndOptimize(context, runtimeLlvmModules)
|
||||
context.config.debug -> Raw(runtimeLlvmModules)
|
||||
else -> Raw(runtimeLlvmModules)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class RuntimeLinkageStrategyBinaryOption {
|
||||
Raw,
|
||||
Optimize
|
||||
}
|
||||
+6
-1
@@ -356,7 +356,12 @@ internal val rewriteExternalCallsCheckerGlobals = makeKonanModuleOpPhase(
|
||||
internal val bitcodeOptimizationPhase = makeKonanModuleOpPhase(
|
||||
name = "BitcodeOptimization",
|
||||
description = "Optimize bitcode",
|
||||
op = { context, _ -> runLlvmOptimizationPipeline(context) }
|
||||
op = { context, _ ->
|
||||
val config = createLTOFinalPipelineConfig(context)
|
||||
LlvmOptimizationPipeline(config, context.llvmModule!!, context).use {
|
||||
it.run()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
internal val coveragePhase = makeKonanModuleOpPhase(
|
||||
|
||||
+5
@@ -457,3 +457,8 @@ fun LLVMTypeRef.isVectorElementType(): Boolean = when (llvm.LLVMGetTypeKind(this
|
||||
LLVMTypeKind.LLVMDoubleTypeKind -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun LLVMModuleRef.getName(): String = memScoped {
|
||||
val sizeVar = alloc<size_tVar>()
|
||||
LLVMGetModuleIdentifier(this@getName, sizeVar.ptr)!!.toKStringFromUtf8()
|
||||
}
|
||||
Reference in New Issue
Block a user