[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.
This commit is contained in:
Sergey Bogolepov
2023-01-24 16:50:24 +02:00
committed by Space Team
parent 0febffedbd
commit 851d84a865
7 changed files with 114 additions and 28 deletions
@@ -279,7 +279,7 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xtemporary-files-dir", deprecatedName = "--temporary_files_dir", valueDescription = "<path>", 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<String> = 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=<phase>")
var saveLlvmIrDirectory: String? = null
override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap<AnalysisFlag<*>, Any> =
super.configureAnalysisFlags(collector, languageVersion).also {
val optInList = it[AnalysisFlags.optIn] as List<*>
@@ -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)
@@ -161,6 +161,7 @@ class KonanConfigKeys {
val PARTIAL_LINKAGE: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("allows some symbols in klibs be missed")
val TEST_DUMP_OUTPUT_PATH: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("path to a file to dump the list of all available tests")
val OMIT_FRAMEWORK_BINARY: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("do not generate binary in framework")
val SAVE_LLVM_IR_DIRECTORY: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("directory to store LLVM IR from phases")
}
}
@@ -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) {
@@ -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<Action<Unit, NativeGenerationState>> = setOf(nativeLLVMDumper)
internal data class WriteBitcodeFileInput(
override val llvmModule: LLVMModuleRef,
@@ -48,26 +42,26 @@ internal val WriteBitcodeFilePhase = createSimpleNamedCompilerPhase<PhaseContext
LLVMWriteBitcodeToFile(llvmModule, outputFile.canonicalPath)
}
internal val CheckExternalCallsPhase = createSimpleNamedCompilerPhase(
internal val CheckExternalCallsPhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
name = "CheckExternalCalls",
description = "Check external calls",
postactions = llvmPhaseActions,
postactions = getDefaultLlvmModuleActions(),
) { context, _ ->
checkLlvmModuleExternalCalls(context)
}
internal val RewriteExternalCallsCheckerGlobals = createSimpleNamedCompilerPhase(
internal val RewriteExternalCallsCheckerGlobals = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
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<NativeGenerationState, Unit>(
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<NativeGenerationState, Unit>(
name = "Coverage",
description = "Produce coverage information",
postactions = llvmPhaseActions,
postactions = getDefaultLlvmModuleActions(),
op = { context, _ -> runCoveragePass(context) }
)
internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase(
internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
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<NativeGenerationState, Unit>(
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<NativeGenerationState, Unit>(
name = "CStubs",
description = "C stubs compilation",
postactions = llvmPhaseActions,
postactions = getDefaultLlvmModuleActions(),
op = { context, _ -> produceCStubs(context) }
)
internal val LinkBitcodeDependenciesPhase = createSimpleNamedCompilerPhase(
internal val LinkBitcodeDependenciesPhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
name = "LinkBitcodeDependencies",
description = "Link bitcode dependencies",
postactions = llvmPhaseActions,
postactions = getDefaultLlvmModuleActions(),
op = { context, _ -> linkBitcodeDependencies(context) }
)
@@ -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<NativeGenerationState, CodegenInput>(
name = "Codegen",
description = "Code generation",
preactions = getDefaultIrActions(),
postactions = getDefaultIrActions(),
preactions = getDefaultIrActions<CodegenInput, NativeGenerationState>() + getDefaultLlvmModuleActions(),
postactions = getDefaultIrActions<CodegenInput, NativeGenerationState>() + getDefaultLlvmModuleActions(),
op = { generationState, input ->
val context = generationState.context
generationState.objCExport = ObjCExport(
@@ -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
}
}
/**
* Create action that searches context and data for LLVM IR and dumps it.
*/
private fun <Data, Context : PhaseContext> createLlvmDumperAction(): Action<Data, Context> =
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<size_tVar>()
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 <Data, Context : PhaseContext> createLlvmVerifierAction(): Action<Data, Context> =
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 <Data, Context : PhaseContext> 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 <Data, Context : PhaseContext> getDefaultLlvmModuleActions(): Set<Action<Data, Context>> =
setOf(createLlvmDumperAction(), createLlvmVerifierAction())