From 120ca0874059304824c35dfa36e5894bf2034ae9 Mon Sep 17 00:00:00 2001 From: Pavel Kunyavskiy Date: Fri, 27 Jan 2023 14:33:34 +0100 Subject: [PATCH] [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 ^KT-55868 --- .../backend/konan/OptimizationPipeline.kt | 236 ++++++++++-------- .../backend/konan/RuntimeLinkageStrategy.kt | 10 +- .../backend/konan/driver/phases/Bitcode.kt | 65 ++++- 3 files changed, 199 insertions(+), 112 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt index 1197553d151..db8bd93e1f1 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt @@ -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() 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 -} \ No newline at end of file +} diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeLinkageStrategy.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeLinkageStrategy.kt index 343d994d515..6144bb046d3 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeLinkageStrategy.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeLinkageStrategy.kt @@ -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) } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt index 69c980277b4..caafabb6092 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt @@ -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( - 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( + 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( name = "Coverage", @@ -126,7 +159,17 @@ internal fun PhaseEngine.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)