[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:
Pavel Kunyavskiy
2023-01-27 14:33:34 +01:00
committed by Space Cloud
parent 8d62e488fe
commit 120ca08740
3 changed files with 199 additions and 112 deletions
@@ -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
}
}
@@ -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)
}
}
@@ -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)