[K/N] Rework LLVM pipeline
Compiler was running LTO pipeline without preliminary running normal optimisation pipeline. In some cases this lead to unacceptably long compilation. Also, this allows disabling LTO pipeline part, having still quite good performance, but much better compilation time. Co-authored-by: Johan Bay <jobay@google.com> ^KT-55868
This commit is contained in:
committed by
Space Cloud
parent
8d62e488fe
commit
120ca08740
+137
-99
@@ -8,23 +8,6 @@ import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||
import org.jetbrains.kotlin.konan.target.*
|
||||
import java.io.Closeable
|
||||
|
||||
private fun initializeLlvmGlobalPassRegistry() {
|
||||
val passRegistry = LLVMGetGlobalPassRegistry()
|
||||
|
||||
LLVMInitializeCore(passRegistry)
|
||||
LLVMInitializeTransformUtils(passRegistry)
|
||||
LLVMInitializeScalarOpts(passRegistry)
|
||||
LLVMInitializeVectorization(passRegistry)
|
||||
LLVMInitializeInstCombine(passRegistry)
|
||||
LLVMInitializeIPO(passRegistry)
|
||||
LLVMInitializeInstrumentation(passRegistry)
|
||||
LLVMInitializeAnalysis(passRegistry)
|
||||
LLVMInitializeIPA(passRegistry)
|
||||
LLVMInitializeCodeGen(passRegistry)
|
||||
LLVMInitializeTarget(passRegistry)
|
||||
LLVMInitializeObjCARCOpts(passRegistry)
|
||||
}
|
||||
|
||||
enum class LlvmOptimizationLevel(val value: Int) {
|
||||
NONE(0),
|
||||
DEFAULT(1),
|
||||
@@ -54,7 +37,6 @@ data class LlvmPipelineConfig(
|
||||
val makeDeclarationsHidden: Boolean,
|
||||
val objCPasses: Boolean,
|
||||
val inlineThreshold: Int?,
|
||||
val sanitizer: SanitizerKind?,
|
||||
)
|
||||
|
||||
private fun getCpuModel(context: PhaseContext): String {
|
||||
@@ -104,7 +86,6 @@ internal fun createLTOPipelineConfigForRuntime(generationState: NativeGeneration
|
||||
objCPasses = configurables is AppleConfigurables,
|
||||
makeDeclarationsHidden = false,
|
||||
inlineThreshold = tryGetInlineThreshold(generationState),
|
||||
sanitizer = null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -180,24 +161,22 @@ internal fun createLTOFinalPipelineConfig(
|
||||
makeDeclarationsHidden,
|
||||
objcPasses,
|
||||
inlineThreshold,
|
||||
config.sanitizer
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and executes LLVM LTO pipeline on the given [llvmModule].
|
||||
*
|
||||
* Note: this class is intentionally uncoupled from [Context].
|
||||
* Please avoid depending on it.
|
||||
* Prepares and executes LLVM pipeline on the given [llvmModule].
|
||||
*/
|
||||
class LlvmOptimizationPipeline(
|
||||
abstract class LlvmOptimizationPipeline(
|
||||
private val config: LlvmPipelineConfig,
|
||||
private val llvmModule: LLVMModuleRef,
|
||||
private val logger: LoggingContext? = null
|
||||
) : Closeable {
|
||||
private val arena = Arena()
|
||||
abstract fun configurePipeline(config: LlvmPipelineConfig, manager: LLVMPassManagerRef, builder: LLVMPassManagerBuilderRef)
|
||||
open fun executeCustomPreprocessing(config: LlvmPipelineConfig, module: LLVMModuleRef) {}
|
||||
abstract val pipelineName: String
|
||||
|
||||
private val targetMachine: LLVMTargetMachineRef by lazy {
|
||||
private val arena = Arena()
|
||||
private val targetMachineDelegate = lazy {
|
||||
val target = arena.alloc<LLVMTargetRefVar>()
|
||||
val foundLlvmTarget = LLVMGetTargetFromTriple(config.targetTriple, target.ptr, null) == 0
|
||||
check(foundLlvmTarget) { "Cannot get target from triple ${config.targetTriple}." }
|
||||
@@ -211,98 +190,157 @@ class LlvmOptimizationPipeline(
|
||||
config.codeModel)!!
|
||||
}
|
||||
|
||||
private val modulePasses = LLVMCreatePassManager()
|
||||
private val passBuilder = LLVMPassManagerBuilderCreate()
|
||||
private val targetMachine: LLVMTargetMachineRef by targetMachineDelegate
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
when (config.sanitizer) {
|
||||
null -> {}
|
||||
SanitizerKind.ADDRESS -> TODO("Address sanitizer is not supported yet")
|
||||
SanitizerKind.THREAD -> {
|
||||
getFunctions(llvmModule)
|
||||
.filter { LLVMIsDeclaration(it) == 0 }
|
||||
.forEach { addLlvmFunctionEnumAttribute(it, LlvmFunctionAttribute.SanitizeThread) }
|
||||
LLVMAddThreadSanitizerPass(modulePasses)
|
||||
fun execute(llvmModule: LLVMModuleRef) {
|
||||
val passManager = LLVMCreatePassManager()!!
|
||||
val passBuilder = LLVMPassManagerBuilderCreate()!!
|
||||
try {
|
||||
initLLVMOnce()
|
||||
LLVMPassManagerBuilderSetOptLevel(passBuilder, config.optimizationLevel.value)
|
||||
LLVMPassManagerBuilderSetSizeLevel(passBuilder, config.sizeLevel.value)
|
||||
config.inlineThreshold?.let { threshold ->
|
||||
LLVMPassManagerBuilderUseInlinerWithThreshold(passBuilder, threshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
LLVMKotlinAddTargetLibraryInfoWrapperPass(passManager, config.targetTriple)
|
||||
// TargetTransformInfo pass.
|
||||
LLVMAddAnalysisPasses(targetMachine, passManager)
|
||||
|
||||
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()
|
||||
configurePipeline(config, passManager, passBuilder)
|
||||
executeCustomPreprocessing(config, llvmModule)
|
||||
// TODO: how to log content of pass manager?
|
||||
logger?.log {
|
||||
"""
|
||||
Running ${pipelineName} 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(passManager, llvmModule)
|
||||
} finally {
|
||||
LLVMPassManagerBuilderDispose(passBuilder)
|
||||
LLVMDisposePassManager(passManager)
|
||||
}
|
||||
LLVMRunPassManager(modulePasses, llvmModule)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
LLVMPassManagerBuilderDispose(passBuilder)
|
||||
LLVMDisposeTargetMachine(targetMachine)
|
||||
LLVMDisposePassManager(modulePasses)
|
||||
if (targetMachineDelegate.isInitialized()) {
|
||||
LLVMDisposeTargetMachine(targetMachine)
|
||||
}
|
||||
arena.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
@JvmStatic
|
||||
private var isInitialized: Boolean = false
|
||||
|
||||
private fun initLLVMTargets() {
|
||||
memScoped {
|
||||
LLVMKotlinInitializeTargets()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeLlvmGlobalPassRegistry() {
|
||||
val passRegistry = LLVMGetGlobalPassRegistry()
|
||||
|
||||
LLVMInitializeCore(passRegistry)
|
||||
LLVMInitializeTransformUtils(passRegistry)
|
||||
LLVMInitializeScalarOpts(passRegistry)
|
||||
LLVMInitializeVectorization(passRegistry)
|
||||
LLVMInitializeInstCombine(passRegistry)
|
||||
LLVMInitializeIPO(passRegistry)
|
||||
LLVMInitializeInstrumentation(passRegistry)
|
||||
LLVMInitializeAnalysis(passRegistry)
|
||||
LLVMInitializeIPA(passRegistry)
|
||||
LLVMInitializeCodeGen(passRegistry)
|
||||
LLVMInitializeTarget(passRegistry)
|
||||
LLVMInitializeObjCARCOpts(passRegistry)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@JvmStatic
|
||||
fun initLLVMTargets() {
|
||||
fun initLLVMOnce() {
|
||||
if (!isInitialized) {
|
||||
memScoped {
|
||||
LLVMKotlinInitializeTargets()
|
||||
}
|
||||
initLLVMTargets()
|
||||
initializeLlvmGlobalPassRegistry()
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MandatoryOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
|
||||
LlvmOptimizationPipeline(config, logger) {
|
||||
|
||||
override val pipelineName = "Mandatory llvm optimizations"
|
||||
|
||||
override fun configurePipeline(config: LlvmPipelineConfig, manager: LLVMPassManagerRef, builder: LLVMPassManagerBuilderRef) {
|
||||
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(manager)
|
||||
}
|
||||
}
|
||||
|
||||
override fun executeCustomPreprocessing(config: LlvmPipelineConfig, module: LLVMModuleRef) {
|
||||
if (config.makeDeclarationsHidden) {
|
||||
makeVisibilityHiddenLikeLlvmInternalizePass(module)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
|
||||
LlvmOptimizationPipeline(config, logger) {
|
||||
override fun configurePipeline(config: LlvmPipelineConfig, manager: LLVMPassManagerRef, builder: LLVMPassManagerBuilderRef) {
|
||||
LLVMPassManagerBuilderPopulateModulePassManager(builder, manager)
|
||||
LLVMPassManagerBuilderPopulateFunctionPassManager(builder, manager)
|
||||
}
|
||||
|
||||
override val pipelineName = "Module LLVM optimizations"
|
||||
}
|
||||
|
||||
class LTOOptimizationPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
|
||||
LlvmOptimizationPipeline(config, logger) {
|
||||
override fun configurePipeline(config: LlvmPipelineConfig, manager: LLVMPassManagerRef, builder: LLVMPassManagerBuilderRef) {
|
||||
if (config.internalize) {
|
||||
LLVMAddInternalizePass(manager, 0)
|
||||
}
|
||||
|
||||
if (config.globalDce) {
|
||||
LLVMAddGlobalDCEPass(manager)
|
||||
}
|
||||
|
||||
// Pipeline that is similar to `llvm-lto`.
|
||||
LLVMPassManagerBuilderPopulateLTOPassManager(builder, manager, Internalize = 0, RunInliner = 1)
|
||||
}
|
||||
|
||||
override val pipelineName = "LTO LLVM optimizations"
|
||||
}
|
||||
|
||||
class ThreadSanitizerPipeline(config: LlvmPipelineConfig, logger: LoggingContext? = null) :
|
||||
LlvmOptimizationPipeline(config, logger) {
|
||||
override fun configurePipeline(config: LlvmPipelineConfig, manager: LLVMPassManagerRef, builder: LLVMPassManagerBuilderRef) {
|
||||
LLVMAddThreadSanitizerPass(manager)
|
||||
}
|
||||
|
||||
override fun executeCustomPreprocessing(config: LlvmPipelineConfig, module: LLVMModuleRef) {
|
||||
getFunctions(module)
|
||||
.filter { LLVMIsDeclaration(it) == 0 }
|
||||
.forEach { addLlvmFunctionEnumAttribute(it, LlvmFunctionAttribute.SanitizeThread) }
|
||||
}
|
||||
|
||||
override val pipelineName = "Thread sanitizer instrumentation"
|
||||
}
|
||||
|
||||
|
||||
internal fun RelocationModeFlags.currentRelocationMode(context: PhaseContext): RelocationModeFlags.Mode =
|
||||
when (determineLinkerOutput(context)) {
|
||||
LinkerOutputKind.DYNAMIC_LIBRARY -> dynamicLibraryRelocationMode
|
||||
@@ -314,4 +352,4 @@ private fun RelocationModeFlags.Mode.translateToLlvmRelocMode() = when (this) {
|
||||
RelocationModeFlags.Mode.PIC -> LLVMRelocMode.LLVMRelocPIC
|
||||
RelocationModeFlags.Mode.STATIC -> LLVMRelocMode.LLVMRelocStatic
|
||||
RelocationModeFlags.Mode.DEFAULT -> LLVMRelocMode.LLVMRelocDefault
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -52,9 +52,15 @@ internal sealed class RuntimeLinkageStrategy {
|
||||
}
|
||||
}
|
||||
val config = createLTOPipelineConfigForRuntime(generationState)
|
||||
LlvmOptimizationPipeline(config, runtimeModule, generationState).use {
|
||||
it.run()
|
||||
|
||||
// TODO: reconsider pipeline here. Module optimizations instead of LTO can make a lot of sense, but require testing
|
||||
MandatoryOptimizationPipeline(config, generationState).use {
|
||||
it.execute(runtimeModule)
|
||||
}
|
||||
LTOOptimizationPipeline(config, generationState).use {
|
||||
it.execute(runtimeModule)
|
||||
}
|
||||
|
||||
return listOf(runtimeModule)
|
||||
}
|
||||
}
|
||||
|
||||
+54
-11
@@ -8,11 +8,12 @@ package org.jetbrains.kotlin.backend.konan.driver.phases
|
||||
import llvm.LLVMDumpModule
|
||||
import llvm.LLVMModuleRef
|
||||
import llvm.LLVMWriteBitcodeToFile
|
||||
import org.jetbrains.kotlin.backend.common.phaser.ActionState
|
||||
import org.jetbrains.kotlin.backend.common.LoggingContext
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.NativeGenerationState
|
||||
import org.jetbrains.kotlin.backend.konan.checkLlvmModuleExternalCalls
|
||||
import org.jetbrains.kotlin.backend.konan.createLTOFinalPipelineConfig
|
||||
import org.jetbrains.kotlin.backend.konan.driver.BasicPhaseContext
|
||||
import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
|
||||
import org.jetbrains.kotlin.backend.konan.driver.PhaseEngine
|
||||
import org.jetbrains.kotlin.backend.konan.driver.utilities.LlvmIrHolder
|
||||
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.coverage.runCoveragePass
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.verifyModule
|
||||
import org.jetbrains.kotlin.backend.konan.optimizations.RemoveRedundantSafepointsPass
|
||||
import org.jetbrains.kotlin.backend.konan.optimizations.removeMultipleThreadDataLoads
|
||||
import org.jetbrains.kotlin.konan.target.SanitizerKind
|
||||
import java.io.File
|
||||
|
||||
|
||||
@@ -58,16 +60,47 @@ internal val RewriteExternalCallsCheckerGlobals = createSimpleNamedCompilerPhase
|
||||
addFunctionsListSymbolForChecker(context)
|
||||
}
|
||||
|
||||
internal val BitcodeOptimizationPhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
|
||||
name = "BitcodeOptimization",
|
||||
internal class OptimizationState(
|
||||
konanConfig: KonanConfig,
|
||||
val module: LLVMModuleRef,
|
||||
val llvmConfig: LlvmPipelineConfig
|
||||
) : BasicPhaseContext(konanConfig)
|
||||
|
||||
internal fun optimizationPipelinePass(name: String, description: String, pipeline: (LlvmPipelineConfig, LoggingContext) -> LlvmOptimizationPipeline) =
|
||||
createSimpleNamedCompilerPhase<OptimizationState, Unit>(
|
||||
name = name,
|
||||
description = description,
|
||||
postactions = getDefaultLlvmModuleActions(),
|
||||
) { context, _ ->
|
||||
pipeline(context.llvmConfig, context).use {
|
||||
it.execute(context.module)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal val MandatoryBitcodeLLVMPostprocessingPhase = optimizationPipelinePass(
|
||||
name = "MandatoryBitcodeLLVMPostprocessingPhase",
|
||||
description = "Mandatory bitcode llvm postprocessing",
|
||||
pipeline = ::MandatoryOptimizationPipeline,
|
||||
)
|
||||
|
||||
internal val ModuleBitcodeOptimizationPhase = optimizationPipelinePass(
|
||||
name = "ModuleBitcodeOptimization",
|
||||
description = "Optimize bitcode",
|
||||
postactions = getDefaultLlvmModuleActions(),
|
||||
) { context, _ ->
|
||||
val config = createLTOFinalPipelineConfig(context, context.llvm.targetTriple, closedWorld = context.llvmModuleSpecification.isFinal)
|
||||
LlvmOptimizationPipeline(config, context.llvm.module, context).use {
|
||||
it.run()
|
||||
}
|
||||
}
|
||||
pipeline = ::ModuleOptimizationPipeline,
|
||||
)
|
||||
|
||||
internal val LTOBitcodeOptimizationPhase = optimizationPipelinePass(
|
||||
name = "LTOBitcodeOptimization",
|
||||
description = "Runs llvm lto pipeline",
|
||||
pipeline = ::LTOOptimizationPipeline
|
||||
)
|
||||
|
||||
internal val ThreadSanitizerPhase = optimizationPipelinePass(
|
||||
name = "ThreadSanitizer",
|
||||
description = "Prepare to run with thread sanitizer",
|
||||
pipeline = ::ThreadSanitizerPipeline,
|
||||
)
|
||||
|
||||
internal val CoveragePhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
|
||||
name = "Coverage",
|
||||
@@ -126,7 +159,17 @@ internal fun PhaseEngine<NativeGenerationState>.runBitcodePostProcessing() {
|
||||
if (checkExternalCalls) {
|
||||
runPhase(CheckExternalCallsPhase)
|
||||
}
|
||||
runPhase(BitcodeOptimizationPhase)
|
||||
val optimizationConfig = createLTOFinalPipelineConfig(context, context.llvm.targetTriple, closedWorld = context.llvmModuleSpecification.isFinal)
|
||||
useContext(OptimizationState(context.config, context.llvmModule, optimizationConfig)) {
|
||||
it.runPhase(MandatoryBitcodeLLVMPostprocessingPhase)
|
||||
it.runPhase(ModuleBitcodeOptimizationPhase)
|
||||
it.runPhase(LTOBitcodeOptimizationPhase)
|
||||
when (context.config.sanitizer) {
|
||||
SanitizerKind.THREAD -> it.runPhase(ThreadSanitizerPhase)
|
||||
SanitizerKind.ADDRESS -> context.reportCompilationError("Address sanitizer is not supported yet")
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
runPhase(CoveragePhase)
|
||||
if (context.config.memoryModel == MemoryModel.EXPERIMENTAL) {
|
||||
runPhase(RemoveRedundantSafepointsPhase)
|
||||
|
||||
Reference in New Issue
Block a user