brintos

brintos / llvm-project-archived public Read only

0
0
Text · 37.4 KiB · f4c9242 Raw
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 &note : 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 &note : 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