[llvm] [opt] Do not exit on first emitError (PR #194858)
Arseniy Obolenskiy via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 29 06:51:56 PDT 2026
https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/194858
>From d4276202fae2921f382ca131894e852b04bccad0 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Wed, 29 Apr 2026 15:22:20 +0200
Subject: [PATCH] [opt] Do not exit on first emitError
- opt default diagnostic handler calls exit(1) on the first emitError, so later passes don't run and behavior diverges from llc
- set OptDiagnosticHandler that collects all the errors and then exits with non-zero exit code after reporting them
- Fix 2 issues related to the behavior change:
* IR2VecPrinterPass asserted on an invalid vocabulary after the analysis already emitted an error
* InlinerPass::getAdvisor overwrote OwnedAdvisor with the null result of getReplayInlineAdvisor on a missing replay file
related to #142014
---
llvm/lib/Analysis/IR2Vec.cpp | 3 +-
llvm/lib/Transforms/IPO/Inliner.cpp | 20 +++++++------
.../AtomicExpand/AMDGPU/unaligned-atomic.ll | 2 +-
.../NVPTX/safestack-libcall-error.ll | 4 +--
llvm/test/tools/opt/diagnostic-handler.ll | 28 +++++++++++++++++++
llvm/tools/opt/NewPMDriver.cpp | 24 ++++++++++++++++
llvm/tools/opt/NewPMDriver.h | 10 +++++++
llvm/tools/opt/optdriver.cpp | 9 ++++++
8 files changed, 87 insertions(+), 13 deletions(-)
create mode 100644 llvm/test/tools/opt/diagnostic-handler.ll
diff --git a/llvm/lib/Analysis/IR2Vec.cpp b/llvm/lib/Analysis/IR2Vec.cpp
index 55b10c3cf2bc8..e24a08b3b8a65 100644
--- a/llvm/lib/Analysis/IR2Vec.cpp
+++ b/llvm/lib/Analysis/IR2Vec.cpp
@@ -671,7 +671,8 @@ IR2VecVocabAnalysis::run(Module &M, ModuleAnalysisManager &AM) {
PreservedAnalyses IR2VecPrinterPass::run(Module &M,
ModuleAnalysisManager &MAM) {
auto &Vocabulary = MAM.getResult<IR2VecVocabAnalysis>(M);
- assert(Vocabulary.isValid() && "IR2Vec Vocabulary is invalid");
+ if (!Vocabulary.isValid())
+ return PreservedAnalyses::all();
for (Function &F : M) {
auto Emb = Embedder::create(IR2VecEmbeddingKind, F, Vocabulary);
diff --git a/llvm/lib/Transforms/IPO/Inliner.cpp b/llvm/lib/Transforms/IPO/Inliner.cpp
index 7985e74cc5389..ed3fb61a1dfa9 100644
--- a/llvm/lib/Transforms/IPO/Inliner.cpp
+++ b/llvm/lib/Transforms/IPO/Inliner.cpp
@@ -163,14 +163,18 @@ InlinerPass::getAdvisor(const ModuleAnalysisManagerCGSCCProxy::Result &MAM,
InlineContext{LTOPhase, InlinePass::CGSCCInliner});
if (!CGSCCInlineReplayFile.empty())
- OwnedAdvisor = getReplayInlineAdvisor(
- M, FAM, M.getContext(), std::move(OwnedAdvisor),
- ReplayInlinerSettings{CGSCCInlineReplayFile,
- CGSCCInlineReplayScope,
- CGSCCInlineReplayFallback,
- {CGSCCInlineReplayFormat}},
- /*EmitRemarks=*/true,
- InlineContext{LTOPhase, InlinePass::ReplayCGSCCInliner});
+ if (auto ReplayAdvisor = getReplayInlineAdvisor(
+ M, FAM, M.getContext(),
+ std::make_unique<DefaultInlineAdvisor>(
+ M, FAM, getInlineParams(),
+ InlineContext{LTOPhase, InlinePass::CGSCCInliner}),
+ ReplayInlinerSettings{CGSCCInlineReplayFile,
+ CGSCCInlineReplayScope,
+ CGSCCInlineReplayFallback,
+ {CGSCCInlineReplayFormat}},
+ /*EmitRemarks=*/true,
+ InlineContext{LTOPhase, InlinePass::ReplayCGSCCInliner}))
+ OwnedAdvisor = std::move(ReplayAdvisor);
return *OwnedAdvisor;
}
diff --git a/llvm/test/Transforms/AtomicExpand/AMDGPU/unaligned-atomic.ll b/llvm/test/Transforms/AtomicExpand/AMDGPU/unaligned-atomic.ll
index f8f81d0103f83..4ad087181968b 100644
--- a/llvm/test/Transforms/AtomicExpand/AMDGPU/unaligned-atomic.ll
+++ b/llvm/test/Transforms/AtomicExpand/AMDGPU/unaligned-atomic.ll
@@ -6,7 +6,7 @@ define i32 @atomic_load_global_align1(ptr addrspace(1) %ptr) {
ret i32 %val
}
-; FIXME: 2nd error not emitted in next function
+; CHECK: error: unsupported atomic store: instruction alignment 1 is smaller than the required 4-byte alignment for this atomic operation
define void @atomic_store_global_align1(ptr addrspace(1) %ptr, i32 %val) {
store atomic i32 %val, ptr addrspace(1) %ptr monotonic, align 1
ret void
diff --git a/llvm/test/Transforms/SafeStack/NVPTX/safestack-libcall-error.ll b/llvm/test/Transforms/SafeStack/NVPTX/safestack-libcall-error.ll
index e8287639b969a..b8960f5c07898 100644
--- a/llvm/test/Transforms/SafeStack/NVPTX/safestack-libcall-error.ll
+++ b/llvm/test/Transforms/SafeStack/NVPTX/safestack-libcall-error.ll
@@ -1,9 +1,7 @@
; RUN: not opt -disable-output -mtriple=nvptx64-- -mcpu=sm_90 -passes='require<libcall-lowering-info>,safe-stack' %s 2>&1 | FileCheck --implicit-check-not=error %s
; CHECK: error: no location available for safestack pointer address
-
-; FIXME: If opt had more reasonable error handling behavior, this should emit 2 errors
-; xCHECK: error: no libcall available for stackprotector check fail
+; CHECK: error: no libcall available for stackprotector check fail
define void @foo(i32 %t) #0 {
%vla = alloca i32, i32 %t, align 4
call void @baz(ptr %vla)
diff --git a/llvm/test/tools/opt/diagnostic-handler.ll b/llvm/test/tools/opt/diagnostic-handler.ll
new file mode 100644
index 0000000000000..127a6ab8710fc
--- /dev/null
+++ b/llvm/test/tools/opt/diagnostic-handler.ll
@@ -0,0 +1,28 @@
+; Verify that opt's diagnostic handler does not exit on the first error
+; reported via LLVMContext::emitError. opt should continue to run remaining
+; passes (so the print-module pass at the end of the pipeline still emits
+; the module to stdout), then return a non-zero exit status.
+;
+; Without the OptDiagnosticHandler, the first emitError would invoke
+; exit(1) from LLVMContext::diagnose() and the print-module pass would
+; never run, so stdout would be empty.
+
+; RUN: not opt -S -passes='module(sancov-module),print<module-debuginfo>' \
+; RUN: -sanitizer-coverage-level=1 -sanitizer-coverage-stack-depth %s \
+; RUN: >%t.out 2>%t.err
+; RUN: FileCheck --check-prefix=ERR --input-file=%t.err %s
+; RUN: FileCheck --check-prefix=OUT --input-file=%t.out %s
+
+; sancov-module emits an error when '__sancov_lowest_stack' has the wrong
+; type. The pass returns safely and the pipeline continues.
+ at __sancov_lowest_stack = thread_local global i32 0
+
+define i32 @f() {
+ ret i32 0
+}
+
+; ERR: error: '__sancov_lowest_stack' should not be declared by the user
+
+; The print-module pass after sancov-module still runs, proving opt did
+; not exit on the first emitError.
+; OUT: define i32 @f()
diff --git a/llvm/tools/opt/NewPMDriver.cpp b/llvm/tools/opt/NewPMDriver.cpp
index 2f57ae6d045e3..569426a7fd504 100644
--- a/llvm/tools/opt/NewPMDriver.cpp
+++ b/llvm/tools/opt/NewPMDriver.cpp
@@ -23,6 +23,8 @@
#include "llvm/Bitcode/BitcodeWriterPass.h"
#include "llvm/CodeGen/LibcallLoweringInfo.h"
#include "llvm/Config/llvm-config.h"
+#include "llvm/IR/DiagnosticInfo.h"
+#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
@@ -238,6 +240,22 @@ static cl::opt<bool> DisableLoopUnrolling(
"disable-loop-unrolling",
cl::desc("Disable loop unrolling in all relevant passes"), cl::init(false));
+bool OptDiagnosticHandler::handleDiagnostics(const DiagnosticInfo &DI) {
+ DiagnosticHandler::handleDiagnostics(DI);
+ // Mirror the filtering performed by LLVMContext::diagnose's default path:
+ // optimization remarks are gated by the -pass-remarks* flags (isEnabled),
+ // and verbose remarks additionally require hotness data.
+ if (auto *Remark = dyn_cast<DiagnosticInfoOptimizationBase>(&DI))
+ if (!Remark->isEnabled() || (Remark->isVerbose() && !Remark->getHotness()))
+ return true;
+
+ DiagnosticPrinterRawOStream DP(errs());
+ errs() << LLVMContext::getDiagnosticMessagePrefix(DI.getSeverity()) << ": ";
+ DI.print(DP);
+ errs() << "\n";
+ return true;
+}
+
template <typename PassManagerT>
bool tryParsePipelineText(PassBuilder &PB,
const cl::opt<std::string> &PipelineOpt) {
@@ -573,6 +591,12 @@ bool llvm::runPassPipeline(
// Now that we have all of the passes ready, run them.
MPM.run(M, MAM);
+ // If a pass reported an error via LLVMContext::emitError, fail without
+ // writing the output module.
+ if (auto *DH = M.getContext().getDiagHandlerPtr())
+ if (DH->HasErrors)
+ return false;
+
// Declare success.
if (OK != OK_NoOutput) {
Out->keep();
diff --git a/llvm/tools/opt/NewPMDriver.h b/llvm/tools/opt/NewPMDriver.h
index 042d5d4bbfe47..0c60ace68c561 100644
--- a/llvm/tools/opt/NewPMDriver.h
+++ b/llvm/tools/opt/NewPMDriver.h
@@ -20,6 +20,7 @@
#ifndef LLVM_TOOLS_OPT_NEWPMDRIVER_H
#define LLVM_TOOLS_OPT_NEWPMDRIVER_H
+#include "llvm/IR/DiagnosticHandler.h"
#include "llvm/Support/CommandLine.h"
namespace llvm {
@@ -31,6 +32,15 @@ class TargetMachine;
class ToolOutputFile;
class TargetLibraryInfoImpl;
+/// A custom diagnostic handler for opt that consumes diagnostics so that the
+/// default LLVMContext::diagnose() does not call exit() on the first error.
+/// opt can then drain remaining diagnostics from a single pass run and report
+/// them all, mirroring llc's behavior so that opt remains a faithful test
+/// driver for passes that report errors via LLVMContext::emitError.
+struct OptDiagnosticHandler : public DiagnosticHandler {
+ bool handleDiagnostics(const DiagnosticInfo &DI) override;
+};
+
extern cl::opt<bool> DebugifyEach;
extern cl::opt<std::string> DebugifyExport;
diff --git a/llvm/tools/opt/optdriver.cpp b/llvm/tools/opt/optdriver.cpp
index 3e5b1f95b1de9..2e24c2e353ef1 100644
--- a/llvm/tools/opt/optdriver.cpp
+++ b/llvm/tools/opt/optdriver.cpp
@@ -538,6 +538,10 @@ optMain(int argc, char **argv,
if (!DisableDITypeMap)
Context.enableDebugTypeODRUniquing();
+ // Set a diagnostic handler that doesn't exit on the first error so that opt
+ // can drain remaining diagnostics from a single pass run, mirroring llc.
+ Context.setDiagnosticHandler(std::make_unique<OptDiagnosticHandler>());
+
Expected<LLVMRemarkFileHandle> RemarksFileOrErr =
setupLLVMOptimizationRemarks(Context, RemarksFilename, RemarksPasses,
RemarksFormat, RemarksWithHotness,
@@ -974,6 +978,11 @@ optMain(int argc, char **argv,
if (DebugifyEach && !DebugifyExport.empty())
exportDebugifyStats(DebugifyExport, Passes.getDebugifyStatsMap());
+ // If a pass reported an error via LLVMContext::emitError, fail without
+ // writing the output module.
+ if (Context.getDiagHandlerPtr()->HasErrors)
+ return 1;
+
// Declare success.
if (!NoOutput)
Out->keep();
More information about the llvm-commits
mailing list