https://github.com/koparasy updated https://github.com/llvm/llvm-project/pull/199297
>From 9e44a5d9e20cd27ca3e98ac72d7a62930d848a78 Mon Sep 17 00:00:00 2001 From: y <[email protected]> Date: Fri, 22 May 2026 16:09:18 -0700 Subject: [PATCH] [CIR] Route MLIR diagnostics through clang::DiagnosticsEngine Install a scoped MLIR diagnostic handler on the CIRGenerator's MLIRContext that translates mlir::Diagnostic severity and location to clang diag IDs and SourceLocation, so CIR pass / verifier / lowering errors surface in clang's standard format and respect -W/-R flags. Disable MLIR multithreading on that context (DiagnosticsEngine is not thread-safe). Gate the generic CIR-to-CIR fallback errors on hasErrorOccurred() to avoid double-printing alongside specific routed diagnostics. LLVM-side opt/codegen diagnostics are out of scope and will be addressed by sharing BackendConsumer in a follow-up. --- .../clang/Basic/DiagnosticFrontendKinds.td | 7 + clang/include/clang/Basic/DiagnosticGroups.td | 4 + clang/lib/CIR/CodeGen/CIRGenerator.cpp | 5 + .../FrontendAction/CIRDiagnosticHandler.cpp | 94 +++++++ .../CIR/FrontendAction/CIRDiagnosticHandler.h | 54 ++++ clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 20 +- clang/lib/CIR/FrontendAction/CMakeLists.txt | 1 + .../CIR/Diagnostics/mlir-error-routing.cu | 20 ++ .../CIR/CIRDiagnosticHandlerTest.cpp | 247 ++++++++++++++++++ clang/unittests/CIR/CMakeLists.txt | 3 + 10 files changed, 452 insertions(+), 3 deletions(-) create mode 100644 clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp create mode 100644 clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h create mode 100644 clang/test/CIR/Diagnostics/mlir-error-routing.cu create mode 100644 clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index b585f0d3fa9a9..3b7d9a1d56c1b 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -519,4 +519,11 @@ def err_cir_to_cir_transform_failed : Error< def err_cir_verification_failed_pre_passes : Error< "CIR module verification error before running CIR-to-CIR passes">, DefaultFatal; + +// Diagnostics relayed from MLIR-side passes/verifier through the +// clang::DiagnosticsEngine. The full message text comes from MLIR. +def err_cir_mlir_diagnostic : Error<"%0">; +def warn_cir_mlir_diagnostic : Warning<"%0">, InGroup<ClangIR>; +def remark_cir_mlir_diagnostic : Remark<"%0">, InGroup<RemarkClangIR>; +def note_cir_mlir_diagnostic : Note<"%0">; } diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 8031f99419bdc..92fcda5ec6d6a 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1728,6 +1728,10 @@ def OpenMP : DiagGroup<"openmp", [ def SourceUsesOpenACC : DiagGroup<"source-uses-openacc">; def OpenACC : DiagGroup<"openacc", [SourceUsesOpenACC]>; +// ClangIR (CIR) warnings emitted by MLIR-side passes routed through clang. +def ClangIR : DiagGroup<"clangir">; +def RemarkClangIR : DiagGroup<"remark-clangir">; + // Backend warnings. def BackendInlineAsm : DiagGroup<"inline-asm">; def BackendSourceMgr : DiagGroup<"source-mgr">; diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp b/clang/lib/CIR/CodeGen/CIRGenerator.cpp index 31d40c21ef6e1..3b4700d2ed38b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenerator.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp @@ -52,6 +52,11 @@ void CIRGenerator::Initialize(ASTContext &astContext) { this->astContext = &astContext; mlirContext = std::make_unique<mlir::MLIRContext>(); + // Disable MLIR multithreading: clang::DiagnosticsEngine is not thread-safe, + // and the per-context handler installed by CIRGenAction reports diagnostics + // straight through it. CIR's pass pipeline is short enough that we don't + // miss meaningful parallelism here. + mlirContext->disableMultithreading(); mlirContext->loadDialect<mlir::DLTIDialect>(); mlirContext->loadDialect<cir::CIRDialect>(); mlirContext->getOrLoadDialect<mlir::acc::OpenACCDialect>(); diff --git a/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp new file mode 100644 index 0000000000000..7c42f69661788 --- /dev/null +++ b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp @@ -0,0 +1,94 @@ +//===--- CIRDiagnosticHandler.cpp - Route MLIR diags to clang ---------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CIRDiagnosticHandler.h" + +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/MLIRContext.h" + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" + +namespace cir { + +CIRDiagnosticHandler::CIRDiagnosticHandler( + mlir::MLIRContext *ctx, clang::DiagnosticsEngine &diags, + clang::SourceManager &srcMgr, clang::FileManager &fileMgr) + : mlir::ScopedDiagnosticHandler(ctx), Diags(diags), SrcMgr(srcMgr), + FileMgr(fileMgr) { + setHandler([this](mlir::Diagnostic &D) { return handle(D); }); +} + +clang::SourceLocation +CIRDiagnosticHandler::translateLoc(mlir::Location loc) { + // Walk common location wrappers to reach a usable file/line/column triple. + // Anything we can't translate becomes an invalid SourceLocation, which the + // diagnostics engine renders without a source line. + if (auto file = mlir::dyn_cast<mlir::FileLineColLoc>(loc)) { + // SourceManager::translateFileLineCol requires 1-based line/column. + // Module-level locations carry (0, 0); fall through unattached. + if (file.getLine() == 0 || file.getColumn() == 0) + return clang::SourceLocation(); + auto fileRef = FileMgr.getOptionalFileRef(file.getFilename().getValue()); + if (!fileRef) + return clang::SourceLocation(); + return SrcMgr.translateFileLineCol(&fileRef->getFileEntry(), + file.getLine(), file.getColumn()); + } + if (auto fused = mlir::dyn_cast<mlir::FusedLoc>(loc)) { + for (mlir::Location child : fused.getLocations()) { + clang::SourceLocation translated = translateLoc(child); + if (translated.isValid()) + return translated; + } + return clang::SourceLocation(); + } + if (auto callsite = mlir::dyn_cast<mlir::CallSiteLoc>(loc)) + return translateLoc(callsite.getCallee()); + if (auto named = mlir::dyn_cast<mlir::NameLoc>(loc)) + return translateLoc(named.getChildLoc()); + // OpaqueLoc / UnknownLoc and anything else fall through unattached. + return clang::SourceLocation(); +} + +void CIRDiagnosticHandler::emit(mlir::Diagnostic &diag, bool isNote) { + unsigned diagID; + if (isNote) { + diagID = clang::diag::note_cir_mlir_diagnostic; + } else { + switch (diag.getSeverity()) { + case mlir::DiagnosticSeverity::Error: + diagID = clang::diag::err_cir_mlir_diagnostic; + break; + case mlir::DiagnosticSeverity::Warning: + diagID = clang::diag::warn_cir_mlir_diagnostic; + break; + case mlir::DiagnosticSeverity::Remark: + diagID = clang::diag::remark_cir_mlir_diagnostic; + break; + case mlir::DiagnosticSeverity::Note: + diagID = clang::diag::note_cir_mlir_diagnostic; + break; + } + } + Diags.Report(translateLoc(diag.getLocation()), diagID) << diag.str(); +} + +mlir::LogicalResult CIRDiagnosticHandler::handle(mlir::Diagnostic &diag) { + emit(diag, /*isNote=*/false); + for (mlir::Diagnostic ¬e : diag.getNotes()) + emit(note, /*isNote=*/true); + return mlir::success(); +} + +} // namespace cir diff --git a/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h new file mode 100644 index 0000000000000..6a2e99125b62d --- /dev/null +++ b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h @@ -0,0 +1,54 @@ +//===--- CIRDiagnosticHandler.h - Route MLIR diags to clang ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_LIB_CIR_FRONTENDACTION_CIRDIAGNOSTICHANDLER_H +#define CLANG_LIB_CIR_FRONTENDACTION_CIRDIAGNOSTICHANDLER_H + +#include "mlir/IR/Diagnostics.h" + +namespace clang { +class DiagnosticsEngine; +class FileManager; +class SourceLocation; +class SourceManager; +} // namespace clang + +namespace mlir { +class Location; +class MLIRContext; +} // namespace mlir + +namespace cir { + +/// Routes MLIR-side diagnostics emitted during CIR passes, the verifier, and +/// CIR-to-LLVM lowering through the surrounding clang::DiagnosticsEngine. +/// +/// MLIR locations carrying file/line/column info are translated to a +/// clang::SourceLocation via the active SourceManager + FileManager so errors +/// surface in the user's preferred format. Locations that cannot be translated +/// fall back to an invalid clang::SourceLocation. +class CIRDiagnosticHandler : public mlir::ScopedDiagnosticHandler { +public: + CIRDiagnosticHandler(mlir::MLIRContext *ctx, + clang::DiagnosticsEngine &diags, + clang::SourceManager &srcMgr, + clang::FileManager &fileMgr); + +private: + mlir::LogicalResult handle(mlir::Diagnostic &diag); + void emit(mlir::Diagnostic &diag, bool isNote); + clang::SourceLocation translateLoc(mlir::Location loc); + + clang::DiagnosticsEngine &Diags; + clang::SourceManager &SrcMgr; + clang::FileManager &FileMgr; +}; + +} // namespace cir + +#endif // CLANG_LIB_CIR_FRONTENDACTION_CIRDIAGNOSTICHANDLER_H diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp index fccd270a95ccd..d43db7a5e7e27 100644 --- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp +++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang/CIR/FrontendAction/CIRGenAction.h" +#include "CIRDiagnosticHandler.h" #include "mlir/IR/MLIRContext.h" #include "mlir/IR/OwningOpRef.h" #include "clang/Basic/DiagnosticFrontend.h" @@ -82,6 +83,8 @@ class CIRGenConsumer : public clang::ASTConsumer { llvm::LLVMContext &LLVMCtx; SmallVectorImpl<::clang::LinkModule> &LinkModules; + std::optional<CIRDiagnosticHandler> MLIRDiagHandler; + public: CIRGenConsumer(CIRGenAction::OutputType Action, CompilerInstance &CI, CodeGenOptions &CGO, std::unique_ptr<raw_pwrite_stream> OS, @@ -98,6 +101,11 @@ class CIRGenConsumer : public clang::ASTConsumer { assert(!Context && "initialized multiple times"); Context = &Ctx; Gen->Initialize(Ctx); + // Install the MLIR diagnostic handler now that CIRGenerator owns its + // MLIRContext. Lifetime is tied to this consumer, which spans CIRGen, + // CIR-to-CIR passes, and CIR-to-LLVM lowering. + MLIRDiagHandler.emplace(&Gen->getMLIRContext(), CI.getDiagnostics(), + CI.getSourceManager(), CI.getFileManager()); } bool HandleTopLevelDecl(DeclGroupRef D) override { @@ -123,8 +131,11 @@ class CIRGenConsumer : public clang::ASTConsumer { if (!FEOptions.ClangIRDisableCIRVerifier) { if (!Gen->verifyModule()) { - CI.getDiagnostics().Report( - diag::err_cir_verification_failed_pre_passes); + // Verifier output already routed through ClangIRDiagnosticHandler. + // Only emit the generic fatal if nothing more specific was reported. + if (!CI.getDiagnostics().hasErrorOccurred()) + CI.getDiagnostics().Report( + diag::err_cir_verification_failed_pre_passes); llvm::report_fatal_error( "CIR codegen: module verification error before running CIR passes"); return; @@ -140,7 +151,10 @@ class CIRGenConsumer : public clang::ASTConsumer { MlirModule, MlirCtx, C, !FEOptions.ClangIRDisableCIRVerifier, FEOptions.ClangIREnableIdiomRecognizer, CGO.OptimizationLevel > 0) .failed()) { - CI.getDiagnostics().Report(diag::err_cir_to_cir_transform_failed); + // Pass-side errors already routed through ClangIRDiagnosticHandler. + // Skip the generic catch-all if a specific diagnostic was emitted. + if (!CI.getDiagnostics().hasErrorOccurred()) + CI.getDiagnostics().Report(diag::err_cir_to_cir_transform_failed); return; } } diff --git a/clang/lib/CIR/FrontendAction/CMakeLists.txt b/clang/lib/CIR/FrontendAction/CMakeLists.txt index b2d2fc1621693..9577a907c9334 100644 --- a/clang/lib/CIR/FrontendAction/CMakeLists.txt +++ b/clang/lib/CIR/FrontendAction/CMakeLists.txt @@ -9,6 +9,7 @@ get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) add_clang_library(clangCIRFrontendAction CIRGenAction.cpp + CIRDiagnosticHandler.cpp DEPENDS MLIRCIROpsIncGen diff --git a/clang/test/CIR/Diagnostics/mlir-error-routing.cu b/clang/test/CIR/Diagnostics/mlir-error-routing.cu new file mode 100644 index 0000000000000..25759ace8b5bd --- /dev/null +++ b/clang/test/CIR/Diagnostics/mlir-error-routing.cu @@ -0,0 +1,20 @@ +// RUN: not %clang_cc1 -triple x86_64-linux-gnu -fclangir -emit-llvm -x cuda \ +// RUN: -target-sdk-version=12.3 -fcuda-include-gpubinary %t.missing.bin \ +// RUN: %s -o %t.ll 2>&1 | FileCheck %s + +// LoweringPrepare emits an MLIR-side error via mlir::Operation::emitError when +// the requested CUDA gpubinary cannot be opened. With CIRDiagnosticHandler +// installed, that diagnostic surfaces through clang's DiagnosticsEngine in +// clang's standard format (`error: ...`) rather than MLIR's +// `loc("file":N:M): error: ...` default-handler format. + +// CHECK: error: cannot open GPU binary file: {{.*}}.missing.bin +// CHECK-NOT: loc({{.*}}): error: cannot open GPU binary file + +// The generic CIR-to-CIR transform fatal error must NOT be reported on top of +// the specific MLIR-relayed error. CIRGenAction gates the fallback diag on +// clang::DiagnosticsEngine::hasErrorOccurred() so users see one root cause, +// not two. +// CHECK-NOT: error: CIR-to-CIR transformation failed + +__attribute__((global)) void kernel() {} diff --git a/clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp b/clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp new file mode 100644 index 0000000000000..d5917bc1cdf32 --- /dev/null +++ b/clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp @@ -0,0 +1,247 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Unit tests for cir::CIRDiagnosticHandler. +// +// The handler routes MLIR-side diagnostics emitted during CIR passes, the +// verifier, and CIR-to-LLVM lowering through clang::DiagnosticsEngine. These +// tests exercise severity mapping, location translation, note attachment, and +// edge cases (missing files, fused/callsite/name locations, 0-line/0-col) +// without depending on a particular pass-side trigger. +// +//===----------------------------------------------------------------------===// + +#include "../../lib/CIR/FrontendAction/CIRDiagnosticHandler.h" + +#include "mlir/IR/Builders.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/MLIRContext.h" + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" + +#include "llvm/Support/MemoryBuffer.h" +#include "gtest/gtest.h" + +// Don't pull `using namespace clang;` or `using namespace mlir;` here: +// `clang::Diagnostic` and `mlir::Diagnostic` would clash. + +namespace { + +struct CapturedDiag { + clang::DiagnosticsEngine::Level Level; + unsigned ID; + std::string Message; + bool HasLoc; + unsigned Line = 0; + unsigned Column = 0; +}; + +class CapturingConsumer : public clang::DiagnosticConsumer { +public: + std::vector<CapturedDiag> Diags; + + void HandleDiagnostic(clang::DiagnosticsEngine::Level Level, + const clang::Diagnostic &Info) override { + clang::DiagnosticConsumer::HandleDiagnostic(Level, Info); + CapturedDiag CD; + CD.Level = Level; + CD.ID = Info.getID(); + llvm::SmallString<128> Buf; + Info.FormatDiagnostic(Buf); + CD.Message = std::string(Buf); + clang::SourceLocation Loc = Info.getLocation(); + CD.HasLoc = Loc.isValid(); + if (CD.HasLoc && Info.hasSourceManager()) { + clang::PresumedLoc PL = Info.getSourceManager().getPresumedLoc(Loc); + if (PL.isValid()) { + CD.Line = PL.getLine(); + CD.Column = PL.getColumn(); + } + } + Diags.push_back(std::move(CD)); + } +}; + +class CIRDiagnosticHandlerTest : public ::testing::Test { +protected: + CIRDiagnosticHandlerTest() + : Consumer(new CapturingConsumer), + FileMgr(FileMgrOpts), + Diags(clang::DiagnosticIDs::create(), DiagOpts, Consumer.get(), + /*ShouldOwnClient=*/false), + SrcMgr(Diags, FileMgr) { + // Register a virtual file so FileManager::getOptionalFileRef succeeds and + // SourceManager has a buffer for line/col translation. + constexpr llvm::StringLiteral Body = + "line 1\nline 2\nline 3\nline 4\nline 5\n"; + auto Buf = llvm::MemoryBuffer::getMemBufferCopy(Body, srcName()); + clang::FileEntryRef File = + FileMgr.getVirtualFileRef(srcName(), Buf->getBufferSize(), 0); + SrcMgr.overrideFileContents(File, std::move(Buf)); + clang::FileID FID = SrcMgr.getOrCreateFileID(File, clang::SrcMgr::C_User); + SrcMgr.setMainFileID(FID); + } + + static llvm::StringRef srcName() { return "/virtual/file.c"; } + + mlir::Location fileLoc(unsigned Line, unsigned Col, + llvm::StringRef Path = srcName()) { + return mlir::FileLineColLoc::get(&MLIRCtx, Path, Line, Col); + } + + std::unique_ptr<CapturingConsumer> Consumer; + clang::FileSystemOptions FileMgrOpts; + clang::FileManager FileMgr; + clang::DiagnosticOptions DiagOpts; + clang::DiagnosticsEngine Diags; + clang::SourceManager SrcMgr; + mlir::MLIRContext MLIRCtx{mlir::MLIRContext::Threading::DISABLED}; +}; + +TEST_F(CIRDiagnosticHandlerTest, ErrorRoutedWithSeverityAndLocation) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::emitError(fileLoc(2, 3)) << "boom"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + const CapturedDiag &D = Consumer->Diags.front(); + EXPECT_EQ(D.Level, clang::DiagnosticsEngine::Error); + EXPECT_EQ(D.ID, + static_cast<unsigned>(clang::diag::err_cir_mlir_diagnostic)); + EXPECT_EQ(D.Message, "boom"); + EXPECT_TRUE(D.HasLoc); + EXPECT_EQ(D.Line, 2u); + EXPECT_EQ(D.Column, 3u); +} + +TEST_F(CIRDiagnosticHandlerTest, WarningRoutesToWarningId) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::emitWarning(fileLoc(1, 1)) << "soft"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Warning); + EXPECT_EQ(Consumer->Diags[0].ID, + static_cast<unsigned>(clang::diag::warn_cir_mlir_diagnostic)); +} + +TEST_F(CIRDiagnosticHandlerTest, RemarkRoutesToRemarkId) { + // Remarks are off by default. Promote -Rclangir so the consumer sees them. + bool unknownGroup = Diags.setSeverityForGroup( + clang::diag::Flavor::Remark, "remark-clangir", + clang::diag::Severity::Remark); + ASSERT_FALSE(unknownGroup) << "remark-clangir group not registered"; + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::emitRemark(fileLoc(1, 1)) << "fyi"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Remark); + EXPECT_EQ(Consumer->Diags[0].ID, + static_cast<unsigned>(clang::diag::remark_cir_mlir_diagnostic)); +} + +TEST_F(CIRDiagnosticHandlerTest, NotesAttachAfterPrimary) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + { + mlir::InFlightDiagnostic d = mlir::emitError(fileLoc(3, 4)) << "primary"; + d.attachNote(fileLoc(4, 5)) << "extra info"; + } // InFlightDiagnostic dtor reports. + + ASSERT_EQ(Consumer->Diags.size(), 2u); + EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Error); + EXPECT_EQ(Consumer->Diags[0].ID, + static_cast<unsigned>(clang::diag::err_cir_mlir_diagnostic)); + EXPECT_EQ(Consumer->Diags[1].Level, clang::DiagnosticsEngine::Note); + EXPECT_EQ(Consumer->Diags[1].ID, + static_cast<unsigned>(clang::diag::note_cir_mlir_diagnostic)); + EXPECT_EQ(Consumer->Diags[1].Message, "extra info"); + EXPECT_EQ(Consumer->Diags[1].Line, 4u); + EXPECT_EQ(Consumer->Diags[1].Column, 5u); +} + +TEST_F(CIRDiagnosticHandlerTest, ZeroLineColumnFallsBackToInvalidLoc) { + // Module-level locations use line 0 / column 0; SourceManager would assert. + // The handler must guard and emit without a source location. + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::emitError(fileLoc(0, 0)) << "module-level"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_FALSE(Consumer->Diags[0].HasLoc); + EXPECT_EQ(Consumer->Diags[0].Message, "module-level"); +} + +TEST_F(CIRDiagnosticHandlerTest, FileNotInManagerFallsBack) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::emitError(fileLoc(1, 1, "/not/registered.c")) << "stray"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_FALSE(Consumer->Diags[0].HasLoc); + EXPECT_EQ(Consumer->Diags[0].Message, "stray"); +} + +TEST_F(CIRDiagnosticHandlerTest, FusedLocResolvesToFirstTranslatableChild) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::Location unknown = mlir::UnknownLoc::get(&MLIRCtx); + mlir::Location good = fileLoc(2, 1); + mlir::Location fused = mlir::FusedLoc::get({unknown, good}, {}, &MLIRCtx); + mlir::emitError(fused) << "fused"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_TRUE(Consumer->Diags[0].HasLoc); + EXPECT_EQ(Consumer->Diags[0].Line, 2u); +} + +TEST_F(CIRDiagnosticHandlerTest, CallSiteLocFollowsCallee) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::Location callee = fileLoc(3, 2); + mlir::Location caller = fileLoc(5, 1); + mlir::Location cs = mlir::CallSiteLoc::get(callee, caller); + mlir::emitError(cs) << "cs"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_EQ(Consumer->Diags[0].Line, 3u); + EXPECT_EQ(Consumer->Diags[0].Column, 2u); +} + +TEST_F(CIRDiagnosticHandlerTest, NameLocFollowsChild) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::Location child = fileLoc(4, 1); + mlir::Location named = + mlir::NameLoc::get(mlir::StringAttr::get(&MLIRCtx, "tag"), child); + mlir::emitError(named) << "named"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_EQ(Consumer->Diags[0].Line, 4u); +} + +TEST_F(CIRDiagnosticHandlerTest, UnknownLocEmitsUnattached) { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::emitError(mlir::UnknownLoc::get(&MLIRCtx)) << "lost"; + + ASSERT_EQ(Consumer->Diags.size(), 1u); + EXPECT_FALSE(Consumer->Diags[0].HasLoc); +} + +TEST_F(CIRDiagnosticHandlerTest, ScopedLifetimeUnregistersOnDestruction) { + // After the handler scope ends, MLIR's default handler takes over. + // Verify the consumer no longer receives diagnostics through our path. + { + cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr); + mlir::emitError(fileLoc(1, 1)) << "in-scope"; + } + size_t scopedCount = Consumer->Diags.size(); + mlir::emitError(fileLoc(1, 1)) << "out-of-scope"; + // Default handler prints to stderr but does not call our consumer. + EXPECT_EQ(Consumer->Diags.size(), scopedCount); +} + +} // namespace diff --git a/clang/unittests/CIR/CMakeLists.txt b/clang/unittests/CIR/CMakeLists.txt index 81ef87878d33e..6b43389389228 100644 --- a/clang/unittests/CIR/CMakeLists.txt +++ b/clang/unittests/CIR/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(SYSTEM ${MLIR_INCLUDE_DIR}) include_directories(${MLIR_TABLEGEN_OUTPUT_DIR}) add_distinct_clang_unittest(CIRUnitTests + CIRDiagnosticHandlerTest.cpp ControlFlowTest.cpp PointerLikeTest.cpp RecordTypeMetadataTest.cpp @@ -12,6 +13,8 @@ add_distinct_clang_unittest(CIRUnitTests Core LINK_LIBS + clangBasic + clangCIRFrontendAction MLIRCIR CIROpenACCSupport MLIRIR _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
