From 48a684c024cf06995fd0bf20a6b59f2ef1829949 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Fri, 30 Apr 2021 16:53:20 +0300 Subject: [PATCH] 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). --- .../kotlin/backend/konan/CompilerOutput.kt | 8 +- .../backend/konan/llvm/diagnosticReport.kt | 37 ++++++++ .../kotlin/backend/konan/llvm/diagnostics.kt | 95 +++++++++++++++++++ .../kotlin/backend/konan/llvm/linkModules.kt | 14 +++ .../backend/konan/llvm/objc/linkObjC.kt | 2 +- 5 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/diagnosticReport.kt create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/diagnostics.kt create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/linkModules.kt diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt index 4e85a3ecb68..fc1925c1f3d 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt @@ -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) { + 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. + } + } +} diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/diagnostics.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/diagnostics.kt new file mode 100644 index 00000000000..cb693552538 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/diagnostics.kt @@ -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() + + fun add(diagnostic: LlvmDiagnostic) { + diagnostics += diagnostic + } + + fun flush(handler: LlvmDiagnosticHandler) { + handler.handle(diagnostics) + diagnostics.clear() + } +} + +internal interface LlvmDiagnosticHandler { + fun handle(diagnostics: List) +} + +internal inline fun withLlvmDiagnosticHandler(handler: LlvmDiagnosticHandler, block: () -> R): R { + val collector = LlvmDiagnosticCollector() + return try { + withLlvmDiagnosticCollector(collector, block) + } finally { + collector.flush(handler) + } +} + +internal inline fun withLlvmDiagnosticCollector(collector: LlvmDiagnosticCollector, block: () -> R): R { + val handler: LLVMDiagnosticHandler = staticCFunction { diagnostic, context -> + context!!.asStableRef().get().add(createLlvmDiagnostic(diagnostic)) + } + val context = StableRef.create(collector) + return try { + withLlvmDiagnosticHandler(handler, context.asCPointer(), block) + } finally { + context.dispose() + } +} + +internal inline fun 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" + ) +} diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/linkModules.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/linkModules.kt new file mode 100644 index 00000000000..878c1d71d92 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/linkModules.kt @@ -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) + } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt index 6240faaa294..0887b1f0330 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt @@ -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") }