1074 lines · cpp
1//===- Diagnostics.cpp - MLIR Diagnostics ---------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "mlir/IR/Diagnostics.h"10#include "mlir/IR/Attributes.h"11#include "mlir/IR/Location.h"12#include "mlir/IR/MLIRContext.h"13#include "mlir/IR/Operation.h"14#include "mlir/IR/Types.h"15#include "llvm/ADT/MapVector.h"16#include "llvm/ADT/SmallString.h"17#include "llvm/ADT/StringMap.h"18#include "llvm/ADT/TypeSwitch.h"19#include "llvm/Support/Mutex.h"20#include "llvm/Support/PrettyStackTrace.h"21#include "llvm/Support/Regex.h"22#include "llvm/Support/Signals.h"23#include "llvm/Support/SourceMgr.h"24#include "llvm/Support/raw_ostream.h"25#include <optional>26 27using namespace mlir;28using namespace mlir::detail;29 30//===----------------------------------------------------------------------===//31// DiagnosticArgument32//===----------------------------------------------------------------------===//33 34/// Construct from an Attribute.35DiagnosticArgument::DiagnosticArgument(Attribute attr)36 : kind(DiagnosticArgumentKind::Attribute),37 opaqueVal(reinterpret_cast<intptr_t>(attr.getAsOpaquePointer())) {}38 39/// Construct from a Type.40DiagnosticArgument::DiagnosticArgument(Type val)41 : kind(DiagnosticArgumentKind::Type),42 opaqueVal(reinterpret_cast<intptr_t>(val.getAsOpaquePointer())) {}43 44/// Returns this argument as an Attribute.45Attribute DiagnosticArgument::getAsAttribute() const {46 assert(getKind() == DiagnosticArgumentKind::Attribute);47 return Attribute::getFromOpaquePointer(48 reinterpret_cast<const void *>(opaqueVal));49}50 51/// Returns this argument as a Type.52Type DiagnosticArgument::getAsType() const {53 assert(getKind() == DiagnosticArgumentKind::Type);54 return Type::getFromOpaquePointer(reinterpret_cast<const void *>(opaqueVal));55}56 57/// Outputs this argument to a stream.58void DiagnosticArgument::print(raw_ostream &os) const {59 switch (kind) {60 case DiagnosticArgumentKind::Attribute:61 os << getAsAttribute();62 break;63 case DiagnosticArgumentKind::Double:64 os << getAsDouble();65 break;66 case DiagnosticArgumentKind::Integer:67 os << getAsInteger();68 break;69 case DiagnosticArgumentKind::String:70 os << getAsString();71 break;72 case DiagnosticArgumentKind::Type:73 os << '\'' << getAsType() << '\'';74 break;75 case DiagnosticArgumentKind::Unsigned:76 os << getAsUnsigned();77 break;78 }79}80 81//===----------------------------------------------------------------------===//82// Diagnostic83//===----------------------------------------------------------------------===//84 85/// Convert a Twine to a StringRef. Memory used for generating the StringRef is86/// stored in 'strings'.87static StringRef twineToStrRef(const Twine &val,88 std::vector<std::unique_ptr<char[]>> &strings) {89 // Allocate memory to hold this string.90 SmallString<64> data;91 auto strRef = val.toStringRef(data);92 if (strRef.empty())93 return strRef;94 95 strings.push_back(std::unique_ptr<char[]>(new char[strRef.size()]));96 memcpy(&strings.back()[0], strRef.data(), strRef.size());97 // Return a reference to the new string.98 return StringRef(&strings.back()[0], strRef.size());99}100 101/// Stream in a Twine argument.102Diagnostic &Diagnostic::operator<<(char val) { return *this << Twine(val); }103Diagnostic &Diagnostic::operator<<(const Twine &val) {104 arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings)));105 return *this;106}107Diagnostic &Diagnostic::operator<<(Twine &&val) {108 arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings)));109 return *this;110}111 112Diagnostic &Diagnostic::operator<<(StringAttr val) {113 arguments.push_back(DiagnosticArgument(val));114 return *this;115}116 117/// Stream in an OperationName.118Diagnostic &Diagnostic::operator<<(OperationName val) {119 // An OperationName is stored in the context, so we don't need to worry about120 // the lifetime of its data.121 arguments.push_back(DiagnosticArgument(val.getStringRef()));122 return *this;123}124 125/// Adjusts operation printing flags used in diagnostics for the given severity126/// level.127static OpPrintingFlags adjustPrintingFlags(OpPrintingFlags flags,128 DiagnosticSeverity severity) {129 flags.useLocalScope();130 flags.elideLargeElementsAttrs();131 if (severity == DiagnosticSeverity::Error)132 flags.printGenericOpForm();133 return flags;134}135 136/// Stream in an Operation.137Diagnostic &Diagnostic::operator<<(Operation &op) {138 return appendOp(op, OpPrintingFlags());139}140 141Diagnostic &Diagnostic::operator<<(OpWithFlags op) {142 return appendOp(*op.getOperation(), op.flags());143}144 145Diagnostic &Diagnostic::appendOp(Operation &op, const OpPrintingFlags &flags) {146 std::string str;147 llvm::raw_string_ostream os(str);148 op.print(os, adjustPrintingFlags(flags, severity));149 // Print on a new line for better readability if the op will be printed on150 // multiple lines.151 if (str.find('\n') != std::string::npos)152 *this << '\n';153 return *this << str;154}155 156/// Stream in a Value.157Diagnostic &Diagnostic::operator<<(Value val) {158 std::string str;159 llvm::raw_string_ostream os(str);160 val.print(os, adjustPrintingFlags(OpPrintingFlags(), severity));161 return *this << str;162}163 164/// Outputs this diagnostic to a stream.165void Diagnostic::print(raw_ostream &os) const {166 for (auto &arg : getArguments())167 arg.print(os);168}169 170/// Convert the diagnostic to a string.171std::string Diagnostic::str() const {172 std::string str;173 llvm::raw_string_ostream os(str);174 print(os);175 return str;176}177 178/// Attaches a note to this diagnostic. A new location may be optionally179/// provided, if not, then the location defaults to the one specified for this180/// diagnostic. Notes may not be attached to other notes.181Diagnostic &Diagnostic::attachNote(std::optional<Location> noteLoc) {182 // We don't allow attaching notes to notes.183 assert(severity != DiagnosticSeverity::Note &&184 "cannot attach a note to a note");185 186 // If a location wasn't provided then reuse our location.187 if (!noteLoc)188 noteLoc = loc;189 190 /// Append and return a new note.191 notes.push_back(192 std::make_unique<Diagnostic>(*noteLoc, DiagnosticSeverity::Note));193 return *notes.back();194}195 196/// Allow a diagnostic to be converted to 'failure'.197Diagnostic::operator LogicalResult() const { return failure(); }198 199//===----------------------------------------------------------------------===//200// InFlightDiagnostic201//===----------------------------------------------------------------------===//202 203/// Allow an inflight diagnostic to be converted to 'failure', otherwise204/// 'success' if this is an empty diagnostic.205InFlightDiagnostic::operator LogicalResult() const {206 return failure(isActive());207}208 209/// Reports the diagnostic to the engine.210void InFlightDiagnostic::report() {211 // If this diagnostic is still inflight and it hasn't been abandoned, then212 // report it.213 if (isInFlight()) {214 owner->emit(std::move(*impl));215 owner = nullptr;216 }217 impl.reset();218}219 220/// Abandons this diagnostic.221void InFlightDiagnostic::abandon() { owner = nullptr; }222 223//===----------------------------------------------------------------------===//224// DiagnosticEngineImpl225//===----------------------------------------------------------------------===//226 227namespace mlir {228namespace detail {229struct DiagnosticEngineImpl {230 /// Emit a diagnostic using the registered issue handle if present, or with231 /// the default behavior if not.232 void emit(Diagnostic &&diag);233 234 /// A mutex to ensure that diagnostics emission is thread-safe.235 llvm::sys::SmartMutex<true> mutex;236 237 /// These are the handlers used to report diagnostics.238 llvm::SmallMapVector<DiagnosticEngine::HandlerID, DiagnosticEngine::HandlerTy,239 2>240 handlers;241 242 /// This is a unique identifier counter for diagnostic handlers in the243 /// context. This id starts at 1 to allow for 0 to be used as a sentinel.244 DiagnosticEngine::HandlerID uniqueHandlerId = 1;245};246} // namespace detail247} // namespace mlir248 249/// Emit a diagnostic using the registered issue handle if present, or with250/// the default behavior if not.251void DiagnosticEngineImpl::emit(Diagnostic &&diag) {252 llvm::sys::SmartScopedLock<true> lock(mutex);253 254 // Try to process the given diagnostic on one of the registered handlers.255 // Handlers are walked in reverse order, so that the most recent handler is256 // processed first.257 for (auto &handlerIt : llvm::reverse(handlers))258 if (succeeded(handlerIt.second(diag)))259 return;260 261 // Otherwise, if this is an error we emit it to stderr.262 if (diag.getSeverity() != DiagnosticSeverity::Error)263 return;264 265 auto &os = llvm::errs();266 if (!llvm::isa<UnknownLoc>(diag.getLocation()))267 os << diag.getLocation() << ": ";268 os << "error: ";269 270 // The default behavior for errors is to emit them to stderr.271 os << diag << '\n';272 os.flush();273}274 275//===----------------------------------------------------------------------===//276// DiagnosticEngine277//===----------------------------------------------------------------------===//278 279DiagnosticEngine::DiagnosticEngine() : impl(new DiagnosticEngineImpl()) {}280DiagnosticEngine::~DiagnosticEngine() = default;281 282/// Register a new handler for diagnostics to the engine. This function returns283/// a unique identifier for the registered handler, which can be used to284/// unregister this handler at a later time.285auto DiagnosticEngine::registerHandler(HandlerTy handler) -> HandlerID {286 llvm::sys::SmartScopedLock<true> lock(impl->mutex);287 auto uniqueID = impl->uniqueHandlerId++;288 impl->handlers.insert({uniqueID, std::move(handler)});289 return uniqueID;290}291 292/// Erase the registered diagnostic handler with the given identifier.293void DiagnosticEngine::eraseHandler(HandlerID handlerID) {294 llvm::sys::SmartScopedLock<true> lock(impl->mutex);295 impl->handlers.erase(handlerID);296}297 298/// Emit a diagnostic using the registered issue handler if present, or with299/// the default behavior if not.300void DiagnosticEngine::emit(Diagnostic &&diag) {301 assert(diag.getSeverity() != DiagnosticSeverity::Note &&302 "notes should not be emitted directly");303 impl->emit(std::move(diag));304}305 306/// Helper function used to emit a diagnostic with an optionally empty twine307/// message. If the message is empty, then it is not inserted into the308/// diagnostic.309static InFlightDiagnostic310emitDiag(Location location, DiagnosticSeverity severity, const Twine &message) {311 MLIRContext *ctx = location->getContext();312 auto &diagEngine = ctx->getDiagEngine();313 auto diag = diagEngine.emit(location, severity);314 if (!message.isTriviallyEmpty())315 diag << message;316 317 // Add the stack trace as a note if necessary.318 if (ctx->shouldPrintStackTraceOnDiagnostic()) {319 std::string bt;320 {321 llvm::raw_string_ostream stream(bt);322 llvm::sys::PrintStackTrace(stream);323 }324 if (!bt.empty())325 diag.attachNote() << "diagnostic emitted with trace:\n" << bt;326 }327 328 return diag;329}330 331/// Emit an error message using this location.332InFlightDiagnostic mlir::emitError(Location loc) { return emitError(loc, {}); }333InFlightDiagnostic mlir::emitError(Location loc, const Twine &message) {334 return emitDiag(loc, DiagnosticSeverity::Error, message);335}336 337/// Emit a warning message using this location.338InFlightDiagnostic mlir::emitWarning(Location loc) {339 return emitWarning(loc, {});340}341InFlightDiagnostic mlir::emitWarning(Location loc, const Twine &message) {342 return emitDiag(loc, DiagnosticSeverity::Warning, message);343}344 345/// Emit a remark message using this location.346InFlightDiagnostic mlir::emitRemark(Location loc) {347 return emitRemark(loc, {});348}349InFlightDiagnostic mlir::emitRemark(Location loc, const Twine &message) {350 return emitDiag(loc, DiagnosticSeverity::Remark, message);351}352 353//===----------------------------------------------------------------------===//354// ScopedDiagnosticHandler355//===----------------------------------------------------------------------===//356 357ScopedDiagnosticHandler::~ScopedDiagnosticHandler() {358 if (handlerID)359 ctx->getDiagEngine().eraseHandler(handlerID);360}361 362//===----------------------------------------------------------------------===//363// SourceMgrDiagnosticHandler364//===----------------------------------------------------------------------===//365namespace mlir {366namespace detail {367struct SourceMgrDiagnosticHandlerImpl {368 /// Return the SrcManager buffer id for the specified file, or zero if none369 /// can be found.370 unsigned getSourceMgrBufferIDForFile(llvm::SourceMgr &mgr,371 StringRef filename) {372 // Check for an existing mapping to the buffer id for this file.373 auto bufferIt = filenameToBufId.find(filename);374 if (bufferIt != filenameToBufId.end())375 return bufferIt->second;376 377 // Look for a buffer in the manager that has this filename.378 for (unsigned i = 1, e = mgr.getNumBuffers() + 1; i != e; ++i) {379 auto *buf = mgr.getMemoryBuffer(i);380 if (buf->getBufferIdentifier() == filename)381 return filenameToBufId[filename] = i;382 }383 384 // Otherwise, try to load the source file.385 std::string ignored;386 unsigned id = mgr.AddIncludeFile(std::string(filename), SMLoc(), ignored);387 filenameToBufId[filename] = id;388 return id;389 }390 391 /// Mapping between file name and buffer ID's.392 llvm::StringMap<unsigned> filenameToBufId;393};394} // namespace detail395} // namespace mlir396 397/// Return a processable CallSiteLoc from the given location.398static std::optional<CallSiteLoc> getCallSiteLoc(Location loc) {399 if (isa<NameLoc>(loc))400 return getCallSiteLoc(cast<NameLoc>(loc).getChildLoc());401 if (auto callLoc = dyn_cast<CallSiteLoc>(loc))402 return callLoc;403 if (isa<FusedLoc>(loc)) {404 for (auto subLoc : cast<FusedLoc>(loc).getLocations()) {405 if (auto callLoc = getCallSiteLoc(subLoc)) {406 return callLoc;407 }408 }409 return std::nullopt;410 }411 return std::nullopt;412}413 414/// Given a diagnostic kind, returns the LLVM DiagKind.415static llvm::SourceMgr::DiagKind getDiagKind(DiagnosticSeverity kind) {416 switch (kind) {417 case DiagnosticSeverity::Note:418 return llvm::SourceMgr::DK_Note;419 case DiagnosticSeverity::Warning:420 return llvm::SourceMgr::DK_Warning;421 case DiagnosticSeverity::Error:422 return llvm::SourceMgr::DK_Error;423 case DiagnosticSeverity::Remark:424 return llvm::SourceMgr::DK_Remark;425 }426 llvm_unreachable("Unknown DiagnosticSeverity");427}428 429SourceMgrDiagnosticHandler::SourceMgrDiagnosticHandler(430 llvm::SourceMgr &mgr, MLIRContext *ctx, raw_ostream &os,431 ShouldShowLocFn &&shouldShowLocFn)432 : ScopedDiagnosticHandler(ctx), mgr(mgr), os(os),433 shouldShowLocFn(std::move(shouldShowLocFn)),434 impl(new SourceMgrDiagnosticHandlerImpl()) {435 setHandler([this](Diagnostic &diag) { emitDiagnostic(diag); });436}437 438SourceMgrDiagnosticHandler::SourceMgrDiagnosticHandler(439 llvm::SourceMgr &mgr, MLIRContext *ctx, ShouldShowLocFn &&shouldShowLocFn)440 : SourceMgrDiagnosticHandler(mgr, ctx, llvm::errs(),441 std::move(shouldShowLocFn)) {}442 443SourceMgrDiagnosticHandler::~SourceMgrDiagnosticHandler() = default;444 445void SourceMgrDiagnosticHandler::emitDiagnostic(Location loc, Twine message,446 DiagnosticSeverity kind,447 bool displaySourceLine) {448 // Extract a file location from this loc.449 auto fileLoc = loc->findInstanceOf<FileLineColLoc>();450 451 // If one doesn't exist, then print the raw message without a source location.452 if (!fileLoc) {453 std::string str;454 llvm::raw_string_ostream strOS(str);455 if (!llvm::isa<UnknownLoc>(loc))456 strOS << loc << ": ";457 strOS << message;458 return mgr.PrintMessage(os, SMLoc(), getDiagKind(kind), str);459 }460 461 // Otherwise if we are displaying the source line, try to convert the file462 // location to an SMLoc.463 if (displaySourceLine) {464 auto smloc = convertLocToSMLoc(fileLoc);465 if (smloc.isValid())466 return mgr.PrintMessage(os, smloc, getDiagKind(kind), message);467 }468 469 // If the conversion was unsuccessful, create a diagnostic with the file470 // information. We manually combine the line and column to avoid asserts in471 // the constructor of SMDiagnostic that takes a location.472 std::string locStr;473 llvm::raw_string_ostream locOS(locStr);474 locOS << fileLoc.getFilename().getValue() << ":" << fileLoc.getLine() << ":"475 << fileLoc.getColumn();476 llvm::SMDiagnostic diag(locStr, getDiagKind(kind), message.str());477 diag.print(nullptr, os);478}479 480/// Emit the given diagnostic with the held source manager.481void SourceMgrDiagnosticHandler::emitDiagnostic(Diagnostic &diag) {482 SmallVector<std::pair<Location, StringRef>> locationStack;483 auto addLocToStack = [&](Location loc, StringRef locContext) {484 if (std::optional<Location> showableLoc = findLocToShow(loc))485 locationStack.emplace_back(*showableLoc, locContext);486 };487 488 // Add locations to display for this diagnostic.489 Location loc = diag.getLocation();490 addLocToStack(loc, /*locContext=*/{});491 492 // If the diagnostic location was a call site location, add the call stack as493 // well.494 if (auto callLoc = getCallSiteLoc(loc)) {495 // Print the call stack while valid, or until the limit is reached.496 loc = callLoc->getCaller();497 for (unsigned curDepth = 0; curDepth < callStackLimit; ++curDepth) {498 addLocToStack(loc, "called from");499 if ((callLoc = getCallSiteLoc(loc)))500 loc = callLoc->getCaller();501 else502 break;503 }504 }505 506 // If the location stack is empty, use the initial location.507 if (locationStack.empty()) {508 emitDiagnostic(diag.getLocation(), diag.str(), diag.getSeverity());509 510 // Otherwise, use the location stack.511 } else {512 emitDiagnostic(locationStack.front().first, diag.str(), diag.getSeverity());513 for (auto &it : llvm::drop_begin(locationStack))514 emitDiagnostic(it.first, it.second, DiagnosticSeverity::Note);515 }516 517 // Emit each of the notes. Only display the source code if the location is518 // different from the previous location.519 for (auto ¬e : diag.getNotes()) {520 emitDiagnostic(note.getLocation(), note.str(), note.getSeverity(),521 /*displaySourceLine=*/loc != note.getLocation());522 loc = note.getLocation();523 }524}525 526void SourceMgrDiagnosticHandler::setCallStackLimit(unsigned limit) {527 callStackLimit = limit;528}529 530/// Get a memory buffer for the given file, or nullptr if one is not found.531const llvm::MemoryBuffer *532SourceMgrDiagnosticHandler::getBufferForFile(StringRef filename) {533 if (unsigned id = impl->getSourceMgrBufferIDForFile(mgr, filename))534 return mgr.getMemoryBuffer(id);535 return nullptr;536}537 538std::optional<Location>539SourceMgrDiagnosticHandler::findLocToShow(Location loc) {540 if (!shouldShowLocFn)541 return loc;542 if (!shouldShowLocFn(loc))543 return std::nullopt;544 545 // Recurse into the child locations of some of location types.546 return TypeSwitch<LocationAttr, std::optional<Location>>(loc)547 .Case([&](CallSiteLoc callLoc) -> std::optional<Location> {548 // We recurse into the callee of a call site, as the caller will be549 // emitted in a different note on the main diagnostic.550 return findLocToShow(callLoc.getCallee());551 })552 .Case([&](FileLineColLoc) -> std::optional<Location> { return loc; })553 .Case([&](FusedLoc fusedLoc) -> std::optional<Location> {554 // Fused location is unique in that we try to find a sub-location to555 // show, rather than the top-level location itself.556 for (Location childLoc : fusedLoc.getLocations())557 if (std::optional<Location> showableLoc = findLocToShow(childLoc))558 return showableLoc;559 return std::nullopt;560 })561 .Case([&](NameLoc nameLoc) -> std::optional<Location> {562 return findLocToShow(nameLoc.getChildLoc());563 })564 .Case([&](OpaqueLoc opaqueLoc) -> std::optional<Location> {565 // OpaqueLoc always falls back to a different source location.566 return findLocToShow(opaqueLoc.getFallbackLocation());567 })568 .Case([](UnknownLoc) -> std::optional<Location> {569 // Prefer not to show unknown locations.570 return std::nullopt;571 });572}573 574/// Get a memory buffer for the given file, or the main file of the source575/// manager if one doesn't exist. This always returns non-null.576SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) {577 // The column and line may be zero to represent unknown column and/or unknown578 /// line/column information.579 if (loc.getLine() == 0 || loc.getColumn() == 0)580 return SMLoc();581 582 unsigned bufferId = impl->getSourceMgrBufferIDForFile(mgr, loc.getFilename());583 if (!bufferId)584 return SMLoc();585 return mgr.FindLocForLineAndColumn(bufferId, loc.getLine(), loc.getColumn());586}587 588//===----------------------------------------------------------------------===//589// SourceMgrDiagnosticVerifierHandler590//===----------------------------------------------------------------------===//591 592namespace mlir {593namespace detail {594/// This class represents an expected output diagnostic.595struct ExpectedDiag {596 ExpectedDiag(DiagnosticSeverity kind, unsigned lineNo, SMLoc fileLoc,597 StringRef substring)598 : kind(kind), lineNo(lineNo), fileLoc(fileLoc), substring(substring) {}599 600 /// Emit an error at the location referenced by this diagnostic.601 LogicalResult emitError(raw_ostream &os, llvm::SourceMgr &mgr,602 const Twine &msg) {603 SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +604 substring.size()));605 mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg, range);606 return failure();607 }608 609 /// Returns true if this diagnostic matches the given string.610 bool match(StringRef str) const {611 // If this isn't a regex diagnostic, we simply check if the string was612 // contained.613 if (substringRegex)614 return substringRegex->match(str);615 return str.contains(substring);616 }617 618 /// Compute the regex matcher for this diagnostic, using the provided stream619 /// and manager to emit diagnostics as necessary.620 LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr) {621 std::string regexStr;622 llvm::raw_string_ostream regexOS(regexStr);623 StringRef strToProcess = substring;624 while (!strToProcess.empty()) {625 // Find the next regex block.626 size_t regexIt = strToProcess.find("{{");627 if (regexIt == StringRef::npos) {628 regexOS << llvm::Regex::escape(strToProcess);629 break;630 }631 regexOS << llvm::Regex::escape(strToProcess.take_front(regexIt));632 strToProcess = strToProcess.drop_front(regexIt + 2);633 634 // Find the end of the regex block.635 size_t regexEndIt = strToProcess.find("}}");636 if (regexEndIt == StringRef::npos)637 return emitError(os, mgr, "found start of regex with no end '}}'");638 StringRef regexStr = strToProcess.take_front(regexEndIt);639 640 // Validate that the regex is actually valid.641 std::string regexError;642 if (!llvm::Regex(regexStr).isValid(regexError))643 return emitError(os, mgr, "invalid regex: " + regexError);644 645 regexOS << '(' << regexStr << ')';646 strToProcess = strToProcess.drop_front(regexEndIt + 2);647 }648 substringRegex = llvm::Regex(regexStr);649 return success();650 }651 652 /// The severity of the diagnosic expected.653 DiagnosticSeverity kind;654 /// The line number the expected diagnostic should be on.655 unsigned lineNo;656 /// The location of the expected diagnostic within the input file.657 SMLoc fileLoc;658 /// A flag indicating if the expected diagnostic has been matched yet.659 bool matched = false;660 /// The substring that is expected to be within the diagnostic.661 StringRef substring;662 /// An optional regex matcher, if the expected diagnostic sub-string was a663 /// regex string.664 std::optional<llvm::Regex> substringRegex;665};666 667struct SourceMgrDiagnosticVerifierHandlerImpl {668 SourceMgrDiagnosticVerifierHandlerImpl(669 SourceMgrDiagnosticVerifierHandler::Level level)670 : status(success()), level(level) {}671 672 /// Returns the expected diagnostics for the given source file.673 std::optional<MutableArrayRef<ExpectedDiag>>674 getExpectedDiags(StringRef bufName);675 676 /// Computes the expected diagnostics for the given source buffer.677 MutableArrayRef<ExpectedDiag>678 computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,679 const llvm::MemoryBuffer *buf);680 681 SourceMgrDiagnosticVerifierHandler::Level getVerifyLevel() const {682 return level;683 }684 685 /// The current status of the verifier.686 LogicalResult status;687 688 /// A list of expected diagnostics for each buffer of the source manager.689 llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;690 691 /// A list of expected diagnostics with unknown locations.692 SmallVector<ExpectedDiag, 2> expectedUnknownLocDiags;693 694 /// Regex to match the expected diagnostics format.695 llvm::Regex expected =696 llvm::Regex("expected-(error|note|remark|warning)(-re)? "697 "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$");698 699 /// Verification level.700 SourceMgrDiagnosticVerifierHandler::Level level =701 SourceMgrDiagnosticVerifierHandler::Level::All;702};703} // namespace detail704} // namespace mlir705 706/// Given a diagnostic kind, return a human readable string for it.707static StringRef getDiagKindStr(DiagnosticSeverity kind) {708 switch (kind) {709 case DiagnosticSeverity::Note:710 return "note";711 case DiagnosticSeverity::Warning:712 return "warning";713 case DiagnosticSeverity::Error:714 return "error";715 case DiagnosticSeverity::Remark:716 return "remark";717 }718 llvm_unreachable("Unknown DiagnosticSeverity");719}720 721std::optional<MutableArrayRef<ExpectedDiag>>722SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) {723 auto expectedDiags = expectedDiagsPerFile.find(bufName);724 if (expectedDiags != expectedDiagsPerFile.end())725 return MutableArrayRef<ExpectedDiag>(expectedDiags->second);726 return std::nullopt;727}728 729MutableArrayRef<ExpectedDiag>730SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(731 raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {732 // If the buffer is invalid, return an empty list.733 if (!buf)734 return {};735 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];736 737 // The number of the last line that did not correlate to a designator.738 unsigned lastNonDesignatorLine = 0;739 740 // The indices of designators that apply to the next non designator line.741 SmallVector<unsigned, 1> designatorsForNextLine;742 743 // Scan the file for expected-* designators.744 SmallVector<StringRef, 100> lines;745 buf->getBuffer().split(lines, '\n');746 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {747 SmallVector<StringRef, 4> matches;748 if (!expected.match(lines[lineNo].rtrim(), &matches)) {749 // Check for designators that apply to this line.750 if (!designatorsForNextLine.empty()) {751 for (unsigned diagIndex : designatorsForNextLine)752 expectedDiags[diagIndex].lineNo = lineNo + 1;753 designatorsForNextLine.clear();754 }755 lastNonDesignatorLine = lineNo;756 continue;757 }758 759 // Point to the start of expected-*.760 SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());761 762 DiagnosticSeverity kind;763 if (matches[1] == "error")764 kind = DiagnosticSeverity::Error;765 else if (matches[1] == "warning")766 kind = DiagnosticSeverity::Warning;767 else if (matches[1] == "remark")768 kind = DiagnosticSeverity::Remark;769 else {770 assert(matches[1] == "note");771 kind = DiagnosticSeverity::Note;772 }773 ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);774 775 // Check to see if this is a regex match, i.e. it includes the `-re`.776 if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {777 status = failure();778 continue;779 }780 781 StringRef offsetMatch = matches[3];782 if (!offsetMatch.empty()) {783 offsetMatch = offsetMatch.drop_front(1);784 785 // Get the integer value without the @ and +/- prefix.786 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {787 int offset;788 offsetMatch.drop_front().getAsInteger(0, offset);789 790 if (offsetMatch.front() == '+')791 record.lineNo += offset;792 else793 record.lineNo -= offset;794 } else if (offsetMatch.consume_front("unknown")) {795 // This is matching unknown locations.796 record.fileLoc = SMLoc();797 expectedUnknownLocDiags.emplace_back(std::move(record));798 continue;799 } else if (offsetMatch.consume_front("above")) {800 // If the designator applies 'above' we add it to the last non801 // designator line.802 record.lineNo = lastNonDesignatorLine + 1;803 } else {804 // Otherwise, this is a 'below' designator and applies to the next805 // non-designator line.806 assert(offsetMatch.consume_front("below"));807 designatorsForNextLine.push_back(expectedDiags.size());808 809 // Set the line number to the last in the case that this designator ends810 // up dangling.811 record.lineNo = e;812 }813 }814 expectedDiags.emplace_back(std::move(record));815 }816 return expectedDiags;817}818 819SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler(820 llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level)821 : SourceMgrDiagnosticHandler(srcMgr, ctx, out),822 impl(new SourceMgrDiagnosticVerifierHandlerImpl(level)) {823 // Compute the expected diagnostics for each of the current files in the824 // source manager.825 for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)826 (void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));827 828 registerInContext(ctx);829}830 831SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler(832 llvm::SourceMgr &srcMgr, MLIRContext *ctx, Level level)833 : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs(), level) {}834 835SourceMgrDiagnosticVerifierHandler::~SourceMgrDiagnosticVerifierHandler() {836 // Ensure that all expected diagnostics were handled.837 (void)verify();838}839 840/// Returns the status of the verifier and verifies that all expected841/// diagnostics were emitted. This return success if all diagnostics were842/// verified correctly, failure otherwise.843LogicalResult SourceMgrDiagnosticVerifierHandler::verify() {844 // Verify that all expected errors were seen.845 auto checkExpectedDiags = [&](ExpectedDiag &err) {846 if (!err.matched)847 impl->status =848 err.emitError(os, mgr,849 "expected " + getDiagKindStr(err.kind) + " \"" +850 err.substring + "\" was not produced");851 };852 for (auto &expectedDiagsPair : impl->expectedDiagsPerFile)853 for (auto &err : expectedDiagsPair.second)854 checkExpectedDiags(err);855 for (auto &err : impl->expectedUnknownLocDiags)856 checkExpectedDiags(err);857 impl->expectedDiagsPerFile.clear();858 return impl->status;859}860 861void SourceMgrDiagnosticVerifierHandler::registerInContext(MLIRContext *ctx) {862 ctx->getDiagEngine().registerHandler([&](Diagnostic &diag) {863 // Process the main diagnostics.864 process(diag);865 866 // Process each of the notes.867 for (auto ¬e : diag.getNotes())868 process(note);869 });870}871 872/// Process a single diagnostic.873void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) {874 return process(diag.getLocation(), diag.str(), diag.getSeverity());875}876 877/// Process a diagnostic at a certain location.878void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,879 StringRef msg,880 DiagnosticSeverity kind) {881 FileLineColLoc fileLoc = loc.findInstanceOf<FileLineColLoc>();882 MutableArrayRef<ExpectedDiag> diags;883 884 if (fileLoc) {885 // Get the expected diagnostics for this file.886 if (auto maybeDiags = impl->getExpectedDiags(fileLoc.getFilename())) {887 diags = *maybeDiags;888 } else {889 diags = impl->computeExpectedDiags(890 os, mgr, getBufferForFile(fileLoc.getFilename()));891 }892 } else {893 // Get all expected diagnostics at unknown locations.894 diags = impl->expectedUnknownLocDiags;895 }896 897 // Search for a matching expected diagnostic.898 // If we find something that is close then emit a more specific error.899 ExpectedDiag *nearMiss = nullptr;900 901 // If this was an expected error, remember that we saw it and return.902 for (auto &e : diags) {903 // File line must match (unless it's an unknown location).904 if (fileLoc && fileLoc.getLine() != e.lineNo)905 continue;906 if (e.match(msg)) {907 if (e.kind == kind) {908 e.matched = true;909 return;910 }911 912 // If this only differs based on the diagnostic kind, then consider it913 // to be a near miss.914 nearMiss = &e;915 }916 }917 918 if (impl->getVerifyLevel() == Level::OnlyExpected)919 return;920 921 // Otherwise, emit an error for the near miss.922 if (nearMiss)923 mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error,924 "'" + getDiagKindStr(kind) +925 "' diagnostic emitted when expecting a '" +926 getDiagKindStr(nearMiss->kind) + "'");927 else928 emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,929 DiagnosticSeverity::Error);930 impl->status = failure();931}932 933//===----------------------------------------------------------------------===//934// ParallelDiagnosticHandler935//===----------------------------------------------------------------------===//936 937namespace mlir {938namespace detail {939struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry {940 struct ThreadDiagnostic {941 ThreadDiagnostic(size_t id, Diagnostic diag)942 : id(id), diag(std::move(diag)) {}943 bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; }944 945 /// The id for this diagnostic, this is used for ordering.946 /// Note: This id corresponds to the ordered position of the current element947 /// being processed by a given thread.948 size_t id;949 950 /// The diagnostic.951 Diagnostic diag;952 };953 954 ParallelDiagnosticHandlerImpl(MLIRContext *ctx) : context(ctx) {955 handlerID = ctx->getDiagEngine().registerHandler([this](Diagnostic &diag) {956 uint64_t tid = llvm::get_threadid();957 llvm::sys::SmartScopedLock<true> lock(mutex);958 959 // If this thread is not tracked, then return failure to let another960 // handler process this diagnostic.961 if (!threadToOrderID.count(tid))962 return failure();963 964 // Append a new diagnostic.965 diagnostics.emplace_back(threadToOrderID[tid], std::move(diag));966 return success();967 });968 }969 970 ~ParallelDiagnosticHandlerImpl() override {971 // Erase this handler from the context.972 context->getDiagEngine().eraseHandler(handlerID);973 974 // Early exit if there are no diagnostics, this is the common case.975 if (diagnostics.empty())976 return;977 978 // Emit the diagnostics back to the context.979 emitDiagnostics([&](Diagnostic &diag) {980 return context->getDiagEngine().emit(std::move(diag));981 });982 }983 984 /// Utility method to emit any held diagnostics.985 void emitDiagnostics(llvm::function_ref<void(Diagnostic &)> emitFn) const {986 // Stable sort all of the diagnostics that were emitted. This creates a987 // deterministic ordering for the diagnostics based upon which order id they988 // were emitted for.989 llvm::stable_sort(diagnostics);990 991 // Emit each diagnostic to the context again.992 for (ThreadDiagnostic &diag : diagnostics)993 emitFn(diag.diag);994 }995 996 /// Set the order id for the current thread.997 void setOrderIDForThread(size_t orderID) {998 uint64_t tid = llvm::get_threadid();999 llvm::sys::SmartScopedLock<true> lock(mutex);1000 threadToOrderID[tid] = orderID;1001 }1002 1003 /// Remove the order id for the current thread.1004 void eraseOrderIDForThread() {1005 uint64_t tid = llvm::get_threadid();1006 llvm::sys::SmartScopedLock<true> lock(mutex);1007 threadToOrderID.erase(tid);1008 }1009 1010 /// Dump the current diagnostics that were inflight.1011 void print(raw_ostream &os) const override {1012 // Early exit if there are no diagnostics, this is the common case.1013 if (diagnostics.empty())1014 return;1015 1016 os << "In-Flight Diagnostics:\n";1017 emitDiagnostics([&](const Diagnostic &diag) {1018 os.indent(4);1019 1020 // Print each diagnostic with the format:1021 // "<location>: <kind>: <msg>"1022 if (!llvm::isa<UnknownLoc>(diag.getLocation()))1023 os << diag.getLocation() << ": ";1024 switch (diag.getSeverity()) {1025 case DiagnosticSeverity::Error:1026 os << "error: ";1027 break;1028 case DiagnosticSeverity::Warning:1029 os << "warning: ";1030 break;1031 case DiagnosticSeverity::Note:1032 os << "note: ";1033 break;1034 case DiagnosticSeverity::Remark:1035 os << "remark: ";1036 break;1037 }1038 os << diag << '\n';1039 });1040 }1041 1042 /// A smart mutex to lock access to the internal state.1043 llvm::sys::SmartMutex<true> mutex;1044 1045 /// A mapping between the thread id and the current order id.1046 DenseMap<uint64_t, size_t> threadToOrderID;1047 1048 /// An unordered list of diagnostics that were emitted.1049 mutable std::vector<ThreadDiagnostic> diagnostics;1050 1051 /// The unique id for the parallel handler.1052 DiagnosticEngine::HandlerID handlerID = 0;1053 1054 /// The context to emit the diagnostics to.1055 MLIRContext *context;1056};1057} // namespace detail1058} // namespace mlir1059 1060ParallelDiagnosticHandler::ParallelDiagnosticHandler(MLIRContext *ctx)1061 : impl(new ParallelDiagnosticHandlerImpl(ctx)) {}1062ParallelDiagnosticHandler::~ParallelDiagnosticHandler() = default;1063 1064/// Set the order id for the current thread.1065void ParallelDiagnosticHandler::setOrderIDForThread(size_t orderID) {1066 impl->setOrderIDForThread(orderID);1067}1068 1069/// Remove the order id for the current thread. This removes the thread from1070/// diagnostics tracking.1071void ParallelDiagnosticHandler::eraseOrderIDForThread() {1072 impl->eraseOrderIDForThread();1073}1074