Native: implement custom handling for LLVM diagnostics
Apply it when linking LLVM modules, because otherwise LLVM would terminate the entire compiler process on link errors, which isn't ok, e.g. when embedding the compiler into Gradle daemon (see KT-46358).
This commit is contained in:
committed by
teamcityserver
parent
ee6586fe4f
commit
48a684c024
+4
-4
@@ -50,7 +50,7 @@ internal fun produceCStubs(context: Context) {
|
||||
context.messageCollector,
|
||||
context.inVerbosePhase
|
||||
).forEach {
|
||||
parseAndLinkBitcodeFile(llvmModule, it.absolutePath)
|
||||
parseAndLinkBitcodeFile(context, llvmModule, it.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ private fun linkAllDependencies(context: Context, generatedBitcodeFiles: List<St
|
||||
|
||||
val llvmModule = context.llvmModule!!
|
||||
bitcodeFiles.forEach {
|
||||
parseAndLinkBitcodeFile(llvmModule, it)
|
||||
parseAndLinkBitcodeFile(context, llvmModule, it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +182,9 @@ internal fun produceOutput(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAndLinkBitcodeFile(llvmModule: LLVMModuleRef, path: String) {
|
||||
private fun parseAndLinkBitcodeFile(context: Context, llvmModule: LLVMModuleRef, path: String) {
|
||||
val parsedModule = parseBitcodeFile(path)
|
||||
val failed = LLVMLinkModules2(llvmModule, parsedModule)
|
||||
val failed = llvmLinkModules2(context, llvmModule, parsedModule)
|
||||
if (failed != 0) {
|
||||
throw Error("failed to link $path") // TODO: retrieve error message from LLVM.
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
|
||||
internal open class DefaultLlvmDiagnosticHandler(
|
||||
private val context: Context,
|
||||
private val policy: Policy = Policy.Default
|
||||
) : LlvmDiagnosticHandler {
|
||||
interface Policy {
|
||||
fun suppressWarning(diagnostic: LlvmDiagnostic): Boolean = false
|
||||
|
||||
object Default : Policy
|
||||
}
|
||||
|
||||
override fun handle(diagnostics: List<LlvmDiagnostic>) {
|
||||
diagnostics.forEach {
|
||||
when (it.severity) {
|
||||
LlvmDiagnostic.Severity.ERROR -> throw Error(it.message)
|
||||
LlvmDiagnostic.Severity.WARNING -> if (context.inVerbosePhase || !policy.suppressWarning(it)) {
|
||||
context.messageCollector.report(CompilerMessageSeverity.WARNING, it.message)
|
||||
} else {
|
||||
// else block is required by the compiler.
|
||||
}
|
||||
LlvmDiagnostic.Severity.REMARK,
|
||||
LlvmDiagnostic.Severity.NOTE -> {
|
||||
context.log { "${it.severity}: ${it.message}" }
|
||||
}
|
||||
}.also {} // Make exhaustive.
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.LlvmDiagnostic.Severity
|
||||
|
||||
// Note: we rely on LLVM reporting diagnostics to callback.
|
||||
// This happens during execution of LLVM C++ code, so the callback shouldn't throw Kotlin exceptions.
|
||||
// That's why the supposed operation is: collect diagnostics while running LLVM, then handle after returning to Kotlin.
|
||||
//
|
||||
// By default LLVM prints messages to stderr and terminates the process on errors,
|
||||
// which isn't ok for Kotlin compiler, e.g. when embedding it into Gradle daemon process.
|
||||
|
||||
internal class LlvmDiagnostic(val severity: Severity, val message: String) {
|
||||
enum class Severity {
|
||||
ERROR,
|
||||
WARNING,
|
||||
REMARK,
|
||||
NOTE
|
||||
}
|
||||
}
|
||||
|
||||
internal class LlvmDiagnosticCollector {
|
||||
private val diagnostics = mutableListOf<LlvmDiagnostic>()
|
||||
|
||||
fun add(diagnostic: LlvmDiagnostic) {
|
||||
diagnostics += diagnostic
|
||||
}
|
||||
|
||||
fun flush(handler: LlvmDiagnosticHandler) {
|
||||
handler.handle(diagnostics)
|
||||
diagnostics.clear()
|
||||
}
|
||||
}
|
||||
|
||||
internal interface LlvmDiagnosticHandler {
|
||||
fun handle(diagnostics: List<LlvmDiagnostic>)
|
||||
}
|
||||
|
||||
internal inline fun <R> withLlvmDiagnosticHandler(handler: LlvmDiagnosticHandler, block: () -> R): R {
|
||||
val collector = LlvmDiagnosticCollector()
|
||||
return try {
|
||||
withLlvmDiagnosticCollector(collector, block)
|
||||
} finally {
|
||||
collector.flush(handler)
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <R> withLlvmDiagnosticCollector(collector: LlvmDiagnosticCollector, block: () -> R): R {
|
||||
val handler: LLVMDiagnosticHandler = staticCFunction { diagnostic, context ->
|
||||
context!!.asStableRef<LlvmDiagnosticCollector>().get().add(createLlvmDiagnostic(diagnostic))
|
||||
}
|
||||
val context = StableRef.create(collector)
|
||||
return try {
|
||||
withLlvmDiagnosticHandler(handler, context.asCPointer(), block)
|
||||
} finally {
|
||||
context.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <R> withLlvmDiagnosticHandler(
|
||||
handler: LLVMDiagnosticHandler,
|
||||
context: COpaquePointer,
|
||||
block: () -> R
|
||||
): R {
|
||||
val llvmContext = llvmContext
|
||||
val currentHandler = LLVMContextGetDiagnosticHandler(llvmContext)
|
||||
val currentContext = LLVMContextGetDiagnosticContext(llvmContext)
|
||||
|
||||
return try {
|
||||
LLVMContextSetDiagnosticHandler(llvmContext, handler, context)
|
||||
block()
|
||||
} finally {
|
||||
LLVMContextSetDiagnosticHandler(llvmContext, currentHandler, currentContext)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLlvmDiagnostic(diagnostic: LLVMDiagnosticInfoRef?) = if (diagnostic == null) {
|
||||
LlvmDiagnostic(Severity.ERROR, "Unknown LLVM error")
|
||||
} else {
|
||||
LlvmDiagnostic(
|
||||
severity = when (LLVMGetDiagInfoSeverity(diagnostic)) {
|
||||
LLVMDiagnosticSeverity.LLVMDSError -> Severity.ERROR
|
||||
LLVMDiagnosticSeverity.LLVMDSWarning -> Severity.WARNING
|
||||
LLVMDiagnosticSeverity.LLVMDSRemark -> Severity.REMARK
|
||||
LLVMDiagnosticSeverity.LLVMDSNote -> Severity.NOTE
|
||||
},
|
||||
message = LLVMGetDiagInfoDescription(diagnostic)?.toKString() ?: "Unknown LLVM error"
|
||||
)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
|
||||
internal fun llvmLinkModules2(context: Context, dest: LLVMModuleRef, src: LLVMModuleRef): LLVMBool =
|
||||
withLlvmDiagnosticHandler(DefaultLlvmDiagnosticHandler(context)) {
|
||||
LLVMLinkModules2(dest, src)
|
||||
}
|
||||
+1
-1
@@ -25,7 +25,7 @@ internal fun linkObjC(context: Context) {
|
||||
|
||||
patchBuilder.buildAndApply(parsedModule)
|
||||
|
||||
val failed = LLVMLinkModules2(context.llvmModule!!, parsedModule)
|
||||
val failed = llvmLinkModules2(context, context.llvmModule!!, parsedModule)
|
||||
if (failed != 0) {
|
||||
throw Error("failed to link $bitcodeFile")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user