From 851d84a86510b8eddd35998a72f0121b676b1fd4 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Tue, 24 Jan 2023 16:50:24 +0200 Subject: [PATCH] [K/N] Enable proper LLVM IR dump and verify actions Also introduce -Xsave-llvm-ir-directory argument that should be used instead of -Xtemporary-files-dir as a location for LLVM IR from phases. Motivation for this change: it is simpler to implement and unties LLVM actions from the awful TempFiles class. --- .../arguments/K2NativeCompilerArguments.kt | 5 +- .../kotlin/backend/konan/KonanConfig.kt | 17 ++++- .../backend/konan/KonanConfigurationKeys.kt | 1 + .../backend/konan/SetupConfiguration.kt | 1 + .../backend/konan/driver/phases/Bitcode.kt | 40 +++++----- .../konan/driver/phases/BitcodeGeneration.kt | 5 +- .../driver/utilities/LlvmPassesUtilities.kt | 73 ++++++++++++++++++- 7 files changed, 114 insertions(+), 28 deletions(-) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt index e458b5e3e19..127ddc86a2f 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt @@ -279,7 +279,7 @@ class K2NativeCompilerArguments : CommonCompilerArguments() { @Argument(value = "-Xtemporary-files-dir", deprecatedName = "--temporary_files_dir", valueDescription = "", description = "Save temporary files to the given directory") var temporaryFilesDir: String? = null - @Argument(value = "-Xsave-llvm-ir-after", description = "Save result of Kotlin IR to LLVM IR translation to the temporary files directory.") + @Argument(value = "-Xsave-llvm-ir-after", description = "Save result of Kotlin IR to LLVM IR translation to -Xsave-llvm-ir-directory.") var saveLlvmIrAfter: Array = emptyArray() @Argument(value = "-Xverify-bitcode", deprecatedName = "--verify_bitcode", description = "Verify llvm bitcode after each method") @@ -408,6 +408,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() { @Argument(value = "-Xomit-framework-binary", description = "Omit binary when compiling framework") var omitFrameworkBinary: Boolean = false + @Argument(value = "-Xsave-llvm-ir-directory", description = "Directory that should contain results of -Xsave-llvm-ir-after=") + var saveLlvmIrDirectory: String? = null + override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap, Any> = super.configureAnalysisFlags(collector, languageVersion).also { val optInList = it[AnalysisFlags.optIn] as List<*> diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index e46e8981b17..6535d061df9 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.backend.konan +import com.google.common.io.Files import com.intellij.openapi.project.Project import org.jetbrains.kotlin.backend.common.serialization.linkerissues.UserVisibleIrModulesSupport import org.jetbrains.kotlin.backend.konan.serialization.KonanUserVisibleIrModulesSupport @@ -13,7 +14,6 @@ import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.konan.CURRENT import org.jetbrains.kotlin.konan.CompilerVersion import org.jetbrains.kotlin.konan.MetaVersion @@ -489,6 +489,21 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration } } } + + /** + * Directory to store LLVM IR from -Xsave-llvm-ir-after. + */ + internal val saveLlvmIrDirectory: java.io.File by lazy { + val path = configuration.get(KonanConfigKeys.SAVE_LLVM_IR_DIRECTORY) + if (path == null) { + val tempDir = Files.createTempDir() + configuration.report(CompilerMessageSeverity.WARNING, + "Temporary directory for LLVM IR is ${tempDir.canonicalPath}") + tempDir + } else { + java.io.File(path) + } + } } fun CompilerConfiguration.report(priority: CompilerMessageSeverity, message: String) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt index c93e65c5cac..0f4f091b2ec 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt @@ -161,6 +161,7 @@ class KonanConfigKeys { val PARTIAL_LINKAGE: CompilerConfigurationKey = CompilerConfigurationKey.create("allows some symbols in klibs be missed") val TEST_DUMP_OUTPUT_PATH: CompilerConfigurationKey = CompilerConfigurationKey.create("path to a file to dump the list of all available tests") val OMIT_FRAMEWORK_BINARY: CompilerConfigurationKey = CompilerConfigurationKey.create("do not generate binary in framework") + val SAVE_LLVM_IR_DIRECTORY: CompilerConfigurationKey = CompilerConfigurationKey.create("directory to store LLVM IR from phases") } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt index 829b36f023c..8279e57487d 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt @@ -250,6 +250,7 @@ fun CompilerConfiguration.setupFromArguments(arguments: K2NativeCompilerArgument arguments.testDumpOutputPath?.let { put(TEST_DUMP_OUTPUT_PATH, it) } put(PARTIAL_LINKAGE, arguments.partialLinkage) put(OMIT_FRAMEWORK_BINARY, arguments.omitFrameworkBinary) + putIfNotNull(SAVE_LLVM_IR_DIRECTORY, arguments.saveLlvmIrDirectory) } internal fun CompilerConfiguration.setupCommonOptionsForCaches(konanConfig: KonanConfig) = with(KonanConfigKeys) { 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 a44c0d39143..523ad379f9d 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,7 +8,6 @@ package org.jetbrains.kotlin.backend.konan.driver.phases import llvm.LLVMDumpModule import llvm.LLVMModuleRef import llvm.LLVMWriteBitcodeToFile -import org.jetbrains.kotlin.backend.common.phaser.Action import org.jetbrains.kotlin.backend.common.phaser.ActionState import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.NativeGenerationState @@ -17,6 +16,7 @@ import org.jetbrains.kotlin.backend.konan.createLTOFinalPipelineConfig 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 +import org.jetbrains.kotlin.backend.konan.driver.utilities.getDefaultLlvmModuleActions import org.jetbrains.kotlin.backend.konan.insertAliasToEntryPoint import org.jetbrains.kotlin.backend.konan.llvm.coverage.runCoveragePass import org.jetbrains.kotlin.backend.konan.llvm.verifyModule @@ -24,12 +24,6 @@ import org.jetbrains.kotlin.backend.konan.optimizations.RemoveRedundantSafepoint import org.jetbrains.kotlin.backend.konan.optimizations.removeMultipleThreadDataLoads import java.io.File -private val nativeLLVMDumper = - fun(actionState: ActionState, _: Unit, context: NativeGenerationState) { - llvmIrDumpCallback(actionState, context.llvm.module, context.config, context.tempFiles) - } - -private val llvmPhaseActions: Set> = setOf(nativeLLVMDumper) internal data class WriteBitcodeFileInput( override val llvmModule: LLVMModuleRef, @@ -48,26 +42,26 @@ internal val WriteBitcodeFilePhase = createSimpleNamedCompilerPhase( name = "CheckExternalCalls", description = "Check external calls", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), ) { context, _ -> checkLlvmModuleExternalCalls(context) } -internal val RewriteExternalCallsCheckerGlobals = createSimpleNamedCompilerPhase( +internal val RewriteExternalCallsCheckerGlobals = createSimpleNamedCompilerPhase( name = "RewriteExternalCallsCheckerGlobals", description = "Rewrite globals for external calls checker after optimizer run", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), ) { context, _ -> addFunctionsListSymbolForChecker(context) } -internal val BitcodeOptimizationPhase = createSimpleNamedCompilerPhase( +internal val BitcodeOptimizationPhase = createSimpleNamedCompilerPhase( name = "BitcodeOptimization", description = "Optimize bitcode", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), ) { context, _ -> val config = createLTOFinalPipelineConfig(context, context.llvm.targetTriple, closedWorld = context.llvmModuleSpecification.isFinal) LlvmOptimizationPipeline(config, context.llvm.module, context).use { @@ -75,17 +69,17 @@ internal val BitcodeOptimizationPhase = createSimpleNamedCompilerPhase( } } -internal val CoveragePhase = createSimpleNamedCompilerPhase( +internal val CoveragePhase = createSimpleNamedCompilerPhase( name = "Coverage", description = "Produce coverage information", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), op = { context, _ -> runCoveragePass(context) } ) -internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase( +internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase( name = "RemoveRedundantSafepoints", description = "Remove function prologue safepoints inlined to another function", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), op = { context, _ -> RemoveRedundantSafepointsPass().runOnModule( module = context.llvm.module, @@ -94,24 +88,24 @@ internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase( } ) -internal val OptimizeTLSDataLoadsPhase = createSimpleNamedCompilerPhase( +internal val OptimizeTLSDataLoadsPhase = createSimpleNamedCompilerPhase( name = "OptimizeTLSDataLoads", description = "Optimize multiple loads of thread data", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), op = { context, _ -> removeMultipleThreadDataLoads(context) } ) -internal val CStubsPhase = createSimpleNamedCompilerPhase( +internal val CStubsPhase = createSimpleNamedCompilerPhase( name = "CStubs", description = "C stubs compilation", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), op = { context, _ -> produceCStubs(context) } ) -internal val LinkBitcodeDependenciesPhase = createSimpleNamedCompilerPhase( +internal val LinkBitcodeDependenciesPhase = createSimpleNamedCompilerPhase( name = "LinkBitcodeDependencies", description = "Link bitcode dependencies", - postactions = llvmPhaseActions, + postactions = getDefaultLlvmModuleActions(), op = { context, _ -> linkBitcodeDependencies(context) } ) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/BitcodeGeneration.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/BitcodeGeneration.kt index 87f283ca52a..c51db6aaec8 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/BitcodeGeneration.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/BitcodeGeneration.kt @@ -9,6 +9,7 @@ import llvm.DIFinalize import org.jetbrains.kotlin.backend.konan.NativeGenerationState import org.jetbrains.kotlin.backend.konan.driver.utilities.KotlinBackendIrHolder import org.jetbrains.kotlin.backend.konan.driver.utilities.getDefaultIrActions +import org.jetbrains.kotlin.backend.konan.driver.utilities.getDefaultLlvmModuleActions import org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor import org.jetbrains.kotlin.backend.konan.llvm.Lifetime import org.jetbrains.kotlin.backend.konan.llvm.RTTIGeneratorVisitor @@ -60,8 +61,8 @@ internal data class CodegenInput( internal val CodegenPhase = createSimpleNamedCompilerPhase( name = "Codegen", description = "Code generation", - preactions = getDefaultIrActions(), - postactions = getDefaultIrActions(), + preactions = getDefaultIrActions() + getDefaultLlvmModuleActions(), + postactions = getDefaultIrActions() + getDefaultLlvmModuleActions(), op = { generationState, input -> val context = generationState.context generationState.objCExport = ObjCExport( diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/utilities/LlvmPassesUtilities.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/utilities/LlvmPassesUtilities.kt index 67eeda4e28c..0716e108f7b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/utilities/LlvmPassesUtilities.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/utilities/LlvmPassesUtilities.kt @@ -5,8 +5,18 @@ package org.jetbrains.kotlin.backend.konan.driver.utilities +import kotlinx.cinterop.* +import llvm.LLVMGetModuleIdentifier import llvm.LLVMModuleRef +import llvm.LLVMPrintModuleToFile +import llvm.size_tVar +import org.jetbrains.kotlin.backend.common.phaser.Action +import org.jetbrains.kotlin.backend.common.phaser.ActionState +import org.jetbrains.kotlin.backend.konan.KonanConfigKeys import org.jetbrains.kotlin.backend.konan.driver.PhaseContext +import org.jetbrains.kotlin.backend.konan.llvm.verifyModule +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import java.io.File /** * Implementation of this interface in phase context, input or output @@ -14,4 +24,65 @@ import org.jetbrains.kotlin.backend.konan.driver.PhaseContext */ interface LlvmIrHolder { val llvmModule: LLVMModuleRef -} \ No newline at end of file +} + +/** + * Create action that searches context and data for LLVM IR and dumps it. + */ +private fun createLlvmDumperAction(): Action = + fun(state: ActionState, data: Data, context: Context) { + if (state.phase.name in context.config.configuration.getList(KonanConfigKeys.SAVE_LLVM_IR)) { + val llvmModule = findLlvmModule(data, context) + if (llvmModule == null) { + context.messageCollector.report( + CompilerMessageSeverity.WARNING, + "Cannot dump LLVM IR ${state.beforeOrAfter.name.lowercase()} ${state.phase.name}") + return + } + val moduleName: String = memScoped { + val sizeVar = alloc() + LLVMGetModuleIdentifier(llvmModule, sizeVar.ptr)!!.toKStringFromUtf8() + } + val output = File(context.config.saveLlvmIrDirectory, "$moduleName.${state.phase.name}.ll") + if (LLVMPrintModuleToFile(llvmModule, output.absolutePath, null) != 0) { + error("Can't dump LLVM IR to ${output.absolutePath}") + } + } + } + +/** + * + */ +private fun createLlvmVerifierAction(): Action = + fun(actionState: ActionState, data: Data, context: Context) { + if (!context.config.configuration.getBoolean(KonanConfigKeys.VERIFY_BITCODE)) { + return + } + val llvmModule = findLlvmModule(data, context) + if (llvmModule == null) { + context.messageCollector.report( + CompilerMessageSeverity.WARNING, + "Cannot verify LLVM IR ${actionState.beforeOrAfter.name.lowercase()} ${actionState.phase.name}") + return + } + // TODO: Phase name in message + verifyModule(llvmModule) + } + +/** + * + */ +@Suppress("UNCHECKED_CAST") +private fun findLlvmModule(data: Data, context: Context): LLVMModuleRef? = when { + // TODO: Not safe at all + data is CPointer<*> -> data as LLVMModuleRef + data is LlvmIrHolder -> data.llvmModule + context is LlvmIrHolder -> context.llvmModule + else -> null +} + +/** + * Default set of dump and validate actions for LLVM phases. + */ +internal fun getDefaultLlvmModuleActions(): Set> = + setOf(createLlvmDumperAction(), createLlvmVerifierAction()) \ No newline at end of file