MLIR  21.0.0git
Diagnostics.cpp
Go to the documentation of this file.
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-exception
6 //
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 
27 using namespace mlir;
28 using namespace mlir::detail;
29 
30 //===----------------------------------------------------------------------===//
31 // DiagnosticArgument
32 //===----------------------------------------------------------------------===//
33 
34 /// Construct from an Attribute.
37  opaqueVal(reinterpret_cast<intptr_t>(attr.getAsOpaquePointer())) {}
38 
39 /// Construct from a Type.
42  opaqueVal(reinterpret_cast<intptr_t>(val.getAsOpaquePointer())) {}
43 
44 /// Returns this argument as an Attribute.
48  reinterpret_cast<const void *>(opaqueVal));
49 }
50 
51 /// Returns this argument as a Type.
54  return Type::getFromOpaquePointer(reinterpret_cast<const void *>(opaqueVal));
55 }
56 
57 /// Outputs this argument to a stream.
58 void DiagnosticArgument::print(raw_ostream &os) const {
59  switch (kind) {
61  os << getAsAttribute();
62  break;
64  os << getAsDouble();
65  break;
67  os << getAsInteger();
68  break;
70  os << getAsString();
71  break;
73  os << '\'' << getAsType() << '\'';
74  break;
76  os << getAsUnsigned();
77  break;
78  }
79 }
80 
81 //===----------------------------------------------------------------------===//
82 // Diagnostic
83 //===----------------------------------------------------------------------===//
84 
85 /// Convert a Twine to a StringRef. Memory used for generating the StringRef is
86 /// stored in 'strings'.
87 static 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.
102 Diagnostic &Diagnostic::operator<<(char val) { return *this << Twine(val); }
103 Diagnostic &Diagnostic::operator<<(const Twine &val) {
104  arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings)));
105  return *this;
106 }
108  arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings)));
109  return *this;
110 }
111 
113  arguments.push_back(DiagnosticArgument(val));
114  return *this;
115 }
116 
117 /// Stream in an OperationName.
119  // An OperationName is stored in the context, so we don't need to worry about
120  // 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 severity
126 /// level.
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.
138  return appendOp(op, OpPrintingFlags());
139 }
140 
142  std::string str;
143  llvm::raw_string_ostream os(str);
144  op.print(os, adjustPrintingFlags(flags, severity));
145  // Print on a new line for better readability if the op will be printed on
146  // multiple lines.
147  if (str.find('\n') != std::string::npos)
148  *this << '\n';
149  return *this << str;
150 }
151 
152 /// Stream in a Value.
154  std::string str;
155  llvm::raw_string_ostream os(str);
156  val.print(os, adjustPrintingFlags(OpPrintingFlags(), severity));
157  return *this << str;
158 }
159 
160 /// Outputs this diagnostic to a stream.
161 void Diagnostic::print(raw_ostream &os) const {
162  for (auto &arg : getArguments())
163  arg.print(os);
164 }
165 
166 /// Convert the diagnostic to a string.
167 std::string Diagnostic::str() const {
168  std::string str;
169  llvm::raw_string_ostream os(str);
170  print(os);
171  return str;
172 }
173 
174 /// Attaches a note to this diagnostic. A new location may be optionally
175 /// provided, if not, then the location defaults to the one specified for this
176 /// diagnostic. Notes may not be attached to other notes.
177 Diagnostic &Diagnostic::attachNote(std::optional<Location> noteLoc) {
178  // We don't allow attaching notes to notes.
179  assert(severity != DiagnosticSeverity::Note &&
180  "cannot attach a note to a note");
181 
182  // If a location wasn't provided then reuse our location.
183  if (!noteLoc)
184  noteLoc = loc;
185 
186  /// Append and return a new note.
187  notes.push_back(
188  std::make_unique<Diagnostic>(*noteLoc, DiagnosticSeverity::Note));
189  return *notes.back();
190 }
191 
192 /// Allow a diagnostic to be converted to 'failure'.
193 Diagnostic::operator LogicalResult() const { return failure(); }
194 
195 //===----------------------------------------------------------------------===//
196 // InFlightDiagnostic
197 //===----------------------------------------------------------------------===//
198 
199 /// Allow an inflight diagnostic to be converted to 'failure', otherwise
200 /// 'success' if this is an empty diagnostic.
201 InFlightDiagnostic::operator LogicalResult() const {
202  return failure(isActive());
203 }
204 
205 /// Reports the diagnostic to the engine.
207  // If this diagnostic is still inflight and it hasn't been abandoned, then
208  // report it.
209  if (isInFlight()) {
210  owner->emit(std::move(*impl));
211  owner = nullptr;
212  }
213  impl.reset();
214 }
215 
216 /// Abandons this diagnostic.
217 void InFlightDiagnostic::abandon() { owner = nullptr; }
218 
219 //===----------------------------------------------------------------------===//
220 // DiagnosticEngineImpl
221 //===----------------------------------------------------------------------===//
222 
223 namespace mlir {
224 namespace detail {
226  /// Emit a diagnostic using the registered issue handle if present, or with
227  /// the default behavior if not.
228  void emit(Diagnostic &&diag);
229 
230  /// A mutex to ensure that diagnostics emission is thread-safe.
232 
233  /// These are the handlers used to report diagnostics.
235  2>
237 
238  /// This is a unique identifier counter for diagnostic handlers in the
239  /// context. This id starts at 1 to allow for 0 to be used as a sentinel.
241 };
242 } // namespace detail
243 } // namespace mlir
244 
245 /// Emit a diagnostic using the registered issue handle if present, or with
246 /// the default behavior if not.
247 void DiagnosticEngineImpl::emit(Diagnostic &&diag) {
248  llvm::sys::SmartScopedLock<true> lock(mutex);
249 
250  // Try to process the given diagnostic on one of the registered handlers.
251  // Handlers are walked in reverse order, so that the most recent handler is
252  // processed first.
253  for (auto &handlerIt : llvm::reverse(handlers))
254  if (succeeded(handlerIt.second(diag)))
255  return;
256 
257  // Otherwise, if this is an error we emit it to stderr.
258  if (diag.getSeverity() != DiagnosticSeverity::Error)
259  return;
260 
261  auto &os = llvm::errs();
262  if (!llvm::isa<UnknownLoc>(diag.getLocation()))
263  os << diag.getLocation() << ": ";
264  os << "error: ";
265 
266  // The default behavior for errors is to emit them to stderr.
267  os << diag << '\n';
268  os.flush();
269 }
270 
271 //===----------------------------------------------------------------------===//
272 // DiagnosticEngine
273 //===----------------------------------------------------------------------===//
274 
275 DiagnosticEngine::DiagnosticEngine() : impl(new DiagnosticEngineImpl()) {}
277 
278 /// Register a new handler for diagnostics to the engine. This function returns
279 /// a unique identifier for the registered handler, which can be used to
280 /// unregister this handler at a later time.
282  llvm::sys::SmartScopedLock<true> lock(impl->mutex);
283  auto uniqueID = impl->uniqueHandlerId++;
284  impl->handlers.insert({uniqueID, std::move(handler)});
285  return uniqueID;
286 }
287 
288 /// Erase the registered diagnostic handler with the given identifier.
290  llvm::sys::SmartScopedLock<true> lock(impl->mutex);
291  impl->handlers.erase(handlerID);
292 }
293 
294 /// Emit a diagnostic using the registered issue handler if present, or with
295 /// the default behavior if not.
297  assert(diag.getSeverity() != DiagnosticSeverity::Note &&
298  "notes should not be emitted directly");
299  impl->emit(std::move(diag));
300 }
301 
302 /// Helper function used to emit a diagnostic with an optionally empty twine
303 /// message. If the message is empty, then it is not inserted into the
304 /// diagnostic.
305 static InFlightDiagnostic
306 emitDiag(Location location, DiagnosticSeverity severity, const Twine &message) {
307  MLIRContext *ctx = location->getContext();
308  auto &diagEngine = ctx->getDiagEngine();
309  auto diag = diagEngine.emit(location, severity);
310  if (!message.isTriviallyEmpty())
311  diag << message;
312 
313  // Add the stack trace as a note if necessary.
315  std::string bt;
316  {
317  llvm::raw_string_ostream stream(bt);
318  llvm::sys::PrintStackTrace(stream);
319  }
320  if (!bt.empty())
321  diag.attachNote() << "diagnostic emitted with trace:\n" << bt;
322  }
323 
324  return diag;
325 }
326 
327 /// Emit an error message using this location.
329 InFlightDiagnostic mlir::emitError(Location loc, const Twine &message) {
330  return emitDiag(loc, DiagnosticSeverity::Error, message);
331 }
332 
333 /// Emit a warning message using this location.
335  return emitWarning(loc, {});
336 }
337 InFlightDiagnostic mlir::emitWarning(Location loc, const Twine &message) {
338  return emitDiag(loc, DiagnosticSeverity::Warning, message);
339 }
340 
341 /// Emit a remark message using this location.
343  return emitRemark(loc, {});
344 }
345 InFlightDiagnostic mlir::emitRemark(Location loc, const Twine &message) {
346  return emitDiag(loc, DiagnosticSeverity::Remark, message);
347 }
348 
349 //===----------------------------------------------------------------------===//
350 // ScopedDiagnosticHandler
351 //===----------------------------------------------------------------------===//
352 
354  if (handlerID)
355  ctx->getDiagEngine().eraseHandler(handlerID);
356 }
357 
358 //===----------------------------------------------------------------------===//
359 // SourceMgrDiagnosticHandler
360 //===----------------------------------------------------------------------===//
361 namespace mlir {
362 namespace detail {
364  /// Return the SrcManager buffer id for the specified file, or zero if none
365  /// can be found.
366  unsigned getSourceMgrBufferIDForFile(llvm::SourceMgr &mgr,
367  StringRef filename) {
368  // Check for an existing mapping to the buffer id for this file.
369  auto bufferIt = filenameToBufId.find(filename);
370  if (bufferIt != filenameToBufId.end())
371  return bufferIt->second;
372 
373  // Look for a buffer in the manager that has this filename.
374  for (unsigned i = 1, e = mgr.getNumBuffers() + 1; i != e; ++i) {
375  auto *buf = mgr.getMemoryBuffer(i);
376  if (buf->getBufferIdentifier() == filename)
377  return filenameToBufId[filename] = i;
378  }
379 
380  // Otherwise, try to load the source file.
381  std::string ignored;
382  unsigned id = mgr.AddIncludeFile(std::string(filename), SMLoc(), ignored);
383  filenameToBufId[filename] = id;
384  return id;
385  }
386 
387  /// Mapping between file name and buffer ID's.
388  llvm::StringMap<unsigned> filenameToBufId;
389 };
390 } // namespace detail
391 } // namespace mlir
392 
393 /// Return a processable CallSiteLoc from the given location.
394 static std::optional<CallSiteLoc> getCallSiteLoc(Location loc) {
395  if (dyn_cast<NameLoc>(loc))
396  return getCallSiteLoc(cast<NameLoc>(loc).getChildLoc());
397  if (auto callLoc = dyn_cast<CallSiteLoc>(loc))
398  return callLoc;
399  if (dyn_cast<FusedLoc>(loc)) {
400  for (auto subLoc : cast<FusedLoc>(loc).getLocations()) {
401  if (auto callLoc = getCallSiteLoc(subLoc)) {
402  return callLoc;
403  }
404  }
405  return std::nullopt;
406  }
407  return std::nullopt;
408 }
409 
410 /// Given a diagnostic kind, returns the LLVM DiagKind.
411 static llvm::SourceMgr::DiagKind getDiagKind(DiagnosticSeverity kind) {
412  switch (kind) {
414  return llvm::SourceMgr::DK_Note;
416  return llvm::SourceMgr::DK_Warning;
418  return llvm::SourceMgr::DK_Error;
420  return llvm::SourceMgr::DK_Remark;
421  }
422  llvm_unreachable("Unknown DiagnosticSeverity");
423 }
424 
426  llvm::SourceMgr &mgr, MLIRContext *ctx, raw_ostream &os,
427  ShouldShowLocFn &&shouldShowLocFn)
428  : ScopedDiagnosticHandler(ctx), mgr(mgr), os(os),
429  shouldShowLocFn(std::move(shouldShowLocFn)),
431  setHandler([this](Diagnostic &diag) { emitDiagnostic(diag); });
432 }
433 
435  llvm::SourceMgr &mgr, MLIRContext *ctx, ShouldShowLocFn &&shouldShowLocFn)
436  : SourceMgrDiagnosticHandler(mgr, ctx, llvm::errs(),
437  std::move(shouldShowLocFn)) {}
438 
440 
443  bool displaySourceLine) {
444  // Extract a file location from this loc.
445  auto fileLoc = loc->findInstanceOf<FileLineColLoc>();
446 
447  // If one doesn't exist, then print the raw message without a source location.
448  if (!fileLoc) {
449  std::string str;
450  llvm::raw_string_ostream strOS(str);
451  if (!llvm::isa<UnknownLoc>(loc))
452  strOS << loc << ": ";
453  strOS << message;
454  return mgr.PrintMessage(os, SMLoc(), getDiagKind(kind), str);
455  }
456 
457  // Otherwise if we are displaying the source line, try to convert the file
458  // location to an SMLoc.
459  if (displaySourceLine) {
460  auto smloc = convertLocToSMLoc(fileLoc);
461  if (smloc.isValid())
462  return mgr.PrintMessage(os, smloc, getDiagKind(kind), message);
463  }
464 
465  // If the conversion was unsuccessful, create a diagnostic with the file
466  // information. We manually combine the line and column to avoid asserts in
467  // the constructor of SMDiagnostic that takes a location.
468  std::string locStr;
469  llvm::raw_string_ostream locOS(locStr);
470  locOS << fileLoc.getFilename().getValue() << ":" << fileLoc.getLine() << ":"
471  << fileLoc.getColumn();
472  llvm::SMDiagnostic diag(locStr, getDiagKind(kind), message.str());
473  diag.print(nullptr, os);
474 }
475 
476 /// Emit the given diagnostic with the held source manager.
479  auto addLocToStack = [&](Location loc, StringRef locContext) {
480  if (std::optional<Location> showableLoc = findLocToShow(loc))
481  locationStack.emplace_back(*showableLoc, locContext);
482  };
483 
484  // Add locations to display for this diagnostic.
485  Location loc = diag.getLocation();
486  addLocToStack(loc, /*locContext=*/{});
487 
488  // If the diagnostic location was a call site location, add the call stack as
489  // well.
490  if (auto callLoc = getCallSiteLoc(loc)) {
491  // Print the call stack while valid, or until the limit is reached.
492  loc = callLoc->getCaller();
493  for (unsigned curDepth = 0; curDepth < callStackLimit; ++curDepth) {
494  addLocToStack(loc, "called from");
495  if ((callLoc = getCallSiteLoc(loc)))
496  loc = callLoc->getCaller();
497  else
498  break;
499  }
500  }
501 
502  // If the location stack is empty, use the initial location.
503  if (locationStack.empty()) {
504  emitDiagnostic(diag.getLocation(), diag.str(), diag.getSeverity());
505 
506  // Otherwise, use the location stack.
507  } else {
508  emitDiagnostic(locationStack.front().first, diag.str(), diag.getSeverity());
509  for (auto &it : llvm::drop_begin(locationStack))
510  emitDiagnostic(it.first, it.second, DiagnosticSeverity::Note);
511  }
512 
513  // Emit each of the notes. Only display the source code if the location is
514  // different from the previous location.
515  for (auto &note : diag.getNotes()) {
516  emitDiagnostic(note.getLocation(), note.str(), note.getSeverity(),
517  /*displaySourceLine=*/loc != note.getLocation());
518  loc = note.getLocation();
519  }
520 }
521 
523  callStackLimit = limit;
524 }
525 
526 /// Get a memory buffer for the given file, or nullptr if one is not found.
527 const llvm::MemoryBuffer *
529  if (unsigned id = impl->getSourceMgrBufferIDForFile(mgr, filename))
530  return mgr.getMemoryBuffer(id);
531  return nullptr;
532 }
533 
534 std::optional<Location>
535 SourceMgrDiagnosticHandler::findLocToShow(Location loc) {
536  if (!shouldShowLocFn)
537  return loc;
538  if (!shouldShowLocFn(loc))
539  return std::nullopt;
540 
541  // Recurse into the child locations of some of location types.
543  .Case([&](CallSiteLoc callLoc) -> std::optional<Location> {
544  // We recurse into the callee of a call site, as the caller will be
545  // emitted in a different note on the main diagnostic.
546  return findLocToShow(callLoc.getCallee());
547  })
548  .Case([&](FileLineColLoc) -> std::optional<Location> { return loc; })
549  .Case([&](FusedLoc fusedLoc) -> std::optional<Location> {
550  // Fused location is unique in that we try to find a sub-location to
551  // show, rather than the top-level location itself.
552  for (Location childLoc : fusedLoc.getLocations())
553  if (std::optional<Location> showableLoc = findLocToShow(childLoc))
554  return showableLoc;
555  return std::nullopt;
556  })
557  .Case([&](NameLoc nameLoc) -> std::optional<Location> {
558  return findLocToShow(nameLoc.getChildLoc());
559  })
560  .Case([&](OpaqueLoc opaqueLoc) -> std::optional<Location> {
561  // OpaqueLoc always falls back to a different source location.
562  return findLocToShow(opaqueLoc.getFallbackLocation());
563  })
564  .Case([](UnknownLoc) -> std::optional<Location> {
565  // Prefer not to show unknown locations.
566  return std::nullopt;
567  });
568 }
569 
570 /// Get a memory buffer for the given file, or the main file of the source
571 /// manager if one doesn't exist. This always returns non-null.
572 SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) {
573  // The column and line may be zero to represent unknown column and/or unknown
574  /// line/column information.
575  if (loc.getLine() == 0 || loc.getColumn() == 0)
576  return SMLoc();
577 
578  unsigned bufferId = impl->getSourceMgrBufferIDForFile(mgr, loc.getFilename());
579  if (!bufferId)
580  return SMLoc();
581  return mgr.FindLocForLineAndColumn(bufferId, loc.getLine(), loc.getColumn());
582 }
583 
584 //===----------------------------------------------------------------------===//
585 // SourceMgrDiagnosticVerifierHandler
586 //===----------------------------------------------------------------------===//
587 
588 namespace mlir {
589 namespace detail {
590 /// This class represents an expected output diagnostic.
591 struct ExpectedDiag {
593  StringRef substring)
595 
596  /// Emit an error at the location referenced by this diagnostic.
597  LogicalResult emitError(raw_ostream &os, llvm::SourceMgr &mgr,
598  const Twine &msg) {
599  SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
600  substring.size()));
601  mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg, range);
602  return failure();
603  }
604 
605  /// Returns true if this diagnostic matches the given string.
606  bool match(StringRef str) const {
607  // If this isn't a regex diagnostic, we simply check if the string was
608  // contained.
609  if (substringRegex)
610  return substringRegex->match(str);
611  return str.contains(substring);
612  }
613 
614  /// Compute the regex matcher for this diagnostic, using the provided stream
615  /// and manager to emit diagnostics as necessary.
616  LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr) {
617  std::string regexStr;
618  llvm::raw_string_ostream regexOS(regexStr);
619  StringRef strToProcess = substring;
620  while (!strToProcess.empty()) {
621  // Find the next regex block.
622  size_t regexIt = strToProcess.find("{{");
623  if (regexIt == StringRef::npos) {
624  regexOS << llvm::Regex::escape(strToProcess);
625  break;
626  }
627  regexOS << llvm::Regex::escape(strToProcess.take_front(regexIt));
628  strToProcess = strToProcess.drop_front(regexIt + 2);
629 
630  // Find the end of the regex block.
631  size_t regexEndIt = strToProcess.find("}}");
632  if (regexEndIt == StringRef::npos)
633  return emitError(os, mgr, "found start of regex with no end '}}'");
634  StringRef regexStr = strToProcess.take_front(regexEndIt);
635 
636  // Validate that the regex is actually valid.
637  std::string regexError;
638  if (!llvm::Regex(regexStr).isValid(regexError))
639  return emitError(os, mgr, "invalid regex: " + regexError);
640 
641  regexOS << '(' << regexStr << ')';
642  strToProcess = strToProcess.drop_front(regexEndIt + 2);
643  }
644  substringRegex = llvm::Regex(regexStr);
645  return success();
646  }
647 
648  /// The severity of the diagnosic expected.
650  /// The line number the expected diagnostic should be on.
651  unsigned lineNo;
652  /// The location of the expected diagnostic within the input file.
653  SMLoc fileLoc;
654  /// A flag indicating if the expected diagnostic has been matched yet.
655  bool matched = false;
656  /// The substring that is expected to be within the diagnostic.
657  StringRef substring;
658  /// An optional regex matcher, if the expected diagnostic sub-string was a
659  /// regex string.
660  std::optional<llvm::Regex> substringRegex;
661 };
662 
665 
666  /// Returns the expected diagnostics for the given source file.
667  std::optional<MutableArrayRef<ExpectedDiag>>
668  getExpectedDiags(StringRef bufName);
669 
670  /// Computes the expected diagnostics for the given source buffer.
672  computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,
673  const llvm::MemoryBuffer *buf);
674 
675  /// The current status of the verifier.
676  LogicalResult status;
677 
678  /// A list of expected diagnostics for each buffer of the source manager.
679  llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
680 
681  /// Regex to match the expected diagnostics format.
682  llvm::Regex expected =
683  llvm::Regex("expected-(error|note|remark|warning)(-re)? "
684  "*(@([+-][0-9]+|above|below))? *{{(.*)}}$");
685 };
686 } // namespace detail
687 } // namespace mlir
688 
689 /// Given a diagnostic kind, return a human readable string for it.
691  switch (kind) {
693  return "note";
695  return "warning";
697  return "error";
699  return "remark";
700  }
701  llvm_unreachable("Unknown DiagnosticSeverity");
702 }
703 
704 std::optional<MutableArrayRef<ExpectedDiag>>
705 SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) {
706  auto expectedDiags = expectedDiagsPerFile.find(bufName);
707  if (expectedDiags != expectedDiagsPerFile.end())
708  return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
709  return std::nullopt;
710 }
711 
713 SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
714  raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {
715  // If the buffer is invalid, return an empty list.
716  if (!buf)
717  return std::nullopt;
718  auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
719 
720  // The number of the last line that did not correlate to a designator.
721  unsigned lastNonDesignatorLine = 0;
722 
723  // The indices of designators that apply to the next non designator line.
724  SmallVector<unsigned, 1> designatorsForNextLine;
725 
726  // Scan the file for expected-* designators.
728  buf->getBuffer().split(lines, '\n');
729  for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
731  if (!expected.match(lines[lineNo].rtrim(), &matches)) {
732  // Check for designators that apply to this line.
733  if (!designatorsForNextLine.empty()) {
734  for (unsigned diagIndex : designatorsForNextLine)
735  expectedDiags[diagIndex].lineNo = lineNo + 1;
736  designatorsForNextLine.clear();
737  }
738  lastNonDesignatorLine = lineNo;
739  continue;
740  }
741 
742  // Point to the start of expected-*.
743  SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
744 
746  if (matches[1] == "error")
748  else if (matches[1] == "warning")
750  else if (matches[1] == "remark")
752  else {
753  assert(matches[1] == "note");
755  }
756  ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
757 
758  // Check to see if this is a regex match, i.e. it includes the `-re`.
759  if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {
760  status = failure();
761  continue;
762  }
763 
764  StringRef offsetMatch = matches[3];
765  if (!offsetMatch.empty()) {
766  offsetMatch = offsetMatch.drop_front(1);
767 
768  // Get the integer value without the @ and +/- prefix.
769  if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
770  int offset;
771  offsetMatch.drop_front().getAsInteger(0, offset);
772 
773  if (offsetMatch.front() == '+')
774  record.lineNo += offset;
775  else
776  record.lineNo -= offset;
777  } else if (offsetMatch.consume_front("above")) {
778  // If the designator applies 'above' we add it to the last non
779  // designator line.
780  record.lineNo = lastNonDesignatorLine + 1;
781  } else {
782  // Otherwise, this is a 'below' designator and applies to the next
783  // non-designator line.
784  assert(offsetMatch.consume_front("below"));
785  designatorsForNextLine.push_back(expectedDiags.size());
786 
787  // Set the line number to the last in the case that this designator ends
788  // up dangling.
789  record.lineNo = e;
790  }
791  }
792  expectedDiags.emplace_back(std::move(record));
793  }
794  return expectedDiags;
795 }
796 
798  llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out)
799  : SourceMgrDiagnosticHandler(srcMgr, ctx, out),
801  // Compute the expected diagnostics for each of the current files in the
802  // source manager.
803  for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)
804  (void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));
805 
806  // Register a handler to verify the diagnostics.
807  setHandler([&](Diagnostic &diag) {
808  // Process the main diagnostics.
809  process(diag);
810 
811  // Process each of the notes.
812  for (auto &note : diag.getNotes())
813  process(note);
814  });
815 }
816 
818  llvm::SourceMgr &srcMgr, MLIRContext *ctx)
819  : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs()) {}
820 
822  // Ensure that all expected diagnostics were handled.
823  (void)verify();
824 }
825 
826 /// Returns the status of the verifier and verifies that all expected
827 /// diagnostics were emitted. This return success if all diagnostics were
828 /// verified correctly, failure otherwise.
830  // Verify that all expected errors were seen.
831  for (auto &expectedDiagsPair : impl->expectedDiagsPerFile) {
832  for (auto &err : expectedDiagsPair.second) {
833  if (err.matched)
834  continue;
835  impl->status =
836  err.emitError(os, mgr,
837  "expected " + getDiagKindStr(err.kind) + " \"" +
838  err.substring + "\" was not produced");
839  }
840  }
841  impl->expectedDiagsPerFile.clear();
842  return impl->status;
843 }
844 
845 /// Process a single diagnostic.
846 void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) {
847  auto kind = diag.getSeverity();
848 
849  // Process a FileLineColLoc.
850  if (auto fileLoc = diag.getLocation()->findInstanceOf<FileLineColLoc>())
851  return process(fileLoc, diag.str(), kind);
852 
853  emitDiagnostic(diag.getLocation(),
854  "unexpected " + getDiagKindStr(kind) + ": " + diag.str(),
856  impl->status = failure();
857 }
858 
859 /// Process a FileLineColLoc diagnostic.
860 void SourceMgrDiagnosticVerifierHandler::process(FileLineColLoc loc,
861  StringRef msg,
863  // Get the expected diagnostics for this file.
864  auto diags = impl->getExpectedDiags(loc.getFilename());
865  if (!diags) {
866  diags = impl->computeExpectedDiags(os, mgr,
868  }
869 
870  // Search for a matching expected diagnostic.
871  // If we find something that is close then emit a more specific error.
872  ExpectedDiag *nearMiss = nullptr;
873 
874  // If this was an expected error, remember that we saw it and return.
875  unsigned line = loc.getLine();
876  for (auto &e : *diags) {
877  if (line == e.lineNo && e.match(msg)) {
878  if (e.kind == kind) {
879  e.matched = true;
880  return;
881  }
882 
883  // If this only differs based on the diagnostic kind, then consider it
884  // to be a near miss.
885  nearMiss = &e;
886  }
887  }
888 
889  // Otherwise, emit an error for the near miss.
890  if (nearMiss)
891  mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error,
892  "'" + getDiagKindStr(kind) +
893  "' diagnostic emitted when expecting a '" +
894  getDiagKindStr(nearMiss->kind) + "'");
895  else
896  emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
898  impl->status = failure();
899 }
900 
901 //===----------------------------------------------------------------------===//
902 // ParallelDiagnosticHandler
903 //===----------------------------------------------------------------------===//
904 
905 namespace mlir {
906 namespace detail {
907 struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry {
910  : id(id), diag(std::move(diag)) {}
911  bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; }
912 
913  /// The id for this diagnostic, this is used for ordering.
914  /// Note: This id corresponds to the ordered position of the current element
915  /// being processed by a given thread.
916  size_t id;
917 
918  /// The diagnostic.
920  };
921 
924  uint64_t tid = llvm::get_threadid();
925  llvm::sys::SmartScopedLock<true> lock(mutex);
926 
927  // If this thread is not tracked, then return failure to let another
928  // handler process this diagnostic.
929  if (!threadToOrderID.count(tid))
930  return failure();
931 
932  // Append a new diagnostic.
933  diagnostics.emplace_back(threadToOrderID[tid], std::move(diag));
934  return success();
935  });
936  }
937 
939  // Erase this handler from the context.
941 
942  // Early exit if there are no diagnostics, this is the common case.
943  if (diagnostics.empty())
944  return;
945 
946  // Emit the diagnostics back to the context.
948  return context->getDiagEngine().emit(std::move(diag));
949  });
950  }
951 
952  /// Utility method to emit any held diagnostics.
953  void emitDiagnostics(llvm::function_ref<void(Diagnostic &)> emitFn) const {
954  // Stable sort all of the diagnostics that were emitted. This creates a
955  // deterministic ordering for the diagnostics based upon which order id they
956  // were emitted for.
957  std::stable_sort(diagnostics.begin(), diagnostics.end());
958 
959  // Emit each diagnostic to the context again.
961  emitFn(diag.diag);
962  }
963 
964  /// Set the order id for the current thread.
965  void setOrderIDForThread(size_t orderID) {
966  uint64_t tid = llvm::get_threadid();
967  llvm::sys::SmartScopedLock<true> lock(mutex);
968  threadToOrderID[tid] = orderID;
969  }
970 
971  /// Remove the order id for the current thread.
973  uint64_t tid = llvm::get_threadid();
974  llvm::sys::SmartScopedLock<true> lock(mutex);
975  threadToOrderID.erase(tid);
976  }
977 
978  /// Dump the current diagnostics that were inflight.
979  void print(raw_ostream &os) const override {
980  // Early exit if there are no diagnostics, this is the common case.
981  if (diagnostics.empty())
982  return;
983 
984  os << "In-Flight Diagnostics:\n";
985  emitDiagnostics([&](const Diagnostic &diag) {
986  os.indent(4);
987 
988  // Print each diagnostic with the format:
989  // "<location>: <kind>: <msg>"
990  if (!llvm::isa<UnknownLoc>(diag.getLocation()))
991  os << diag.getLocation() << ": ";
992  switch (diag.getSeverity()) {
993  case DiagnosticSeverity::Error:
994  os << "error: ";
995  break;
996  case DiagnosticSeverity::Warning:
997  os << "warning: ";
998  break;
999  case DiagnosticSeverity::Note:
1000  os << "note: ";
1001  break;
1002  case DiagnosticSeverity::Remark:
1003  os << "remark: ";
1004  break;
1005  }
1006  os << diag << '\n';
1007  });
1008  }
1009 
1010  /// A smart mutex to lock access to the internal state.
1012 
1013  /// A mapping between the thread id and the current order id.
1015 
1016  /// An unordered list of diagnostics that were emitted.
1017  mutable std::vector<ThreadDiagnostic> diagnostics;
1018 
1019  /// The unique id for the parallel handler.
1021 
1022  /// The context to emit the diagnostics to.
1024 };
1025 } // namespace detail
1026 } // namespace mlir
1027 
1029  : impl(new ParallelDiagnosticHandlerImpl(ctx)) {}
1031 
1032 /// Set the order id for the current thread.
1034  impl->setOrderIDForThread(orderID);
1035 }
1036 
1037 /// Remove the order id for the current thread. This removes the thread from
1038 /// diagnostics tracking.
1040  impl->eraseOrderIDForThread();
1041 }
static OpPrintingFlags adjustPrintingFlags(OpPrintingFlags flags, DiagnosticSeverity severity)
Adjusts operation printing flags used in diagnostics for the given severity level.
static InFlightDiagnostic emitDiag(Location location, DiagnosticSeverity severity, const Twine &message)
Helper function used to emit a diagnostic with an optionally empty twine message.
static StringRef getDiagKindStr(DiagnosticSeverity kind)
Given a diagnostic kind, return a human readable string for it.
static StringRef twineToStrRef(const Twine &val, std::vector< std::unique_ptr< char[]>> &strings)
Convert a Twine to a StringRef.
Definition: Diagnostics.cpp:87
static std::optional< CallSiteLoc > getCallSiteLoc(Location loc)
Return a processable CallSiteLoc from the given location.
static llvm::SourceMgr::DiagKind getDiagKind(DiagnosticSeverity kind)
Given a diagnostic kind, returns the LLVM DiagKind.
union mlir::linalg::@1179::ArityGroupAndKind::Kind kind
static std::string diag(const llvm::Value &value)
Attributes are known-constant values of operations.
Definition: Attributes.h:25
MLIRContext * getContext() const
Return the context this attribute belongs to.
Definition: Attributes.cpp:37
static Attribute getFromOpaquePointer(const void *ptr)
Construct an attribute from the opaque pointer representation.
Definition: Attributes.h:93
A variant type that holds a single argument for a diagnostic.
Definition: Diagnostics.h:52
DiagnosticArgumentKind
Enum that represents the different kinds of diagnostic arguments supported.
Definition: Diagnostics.h:84
StringRef getAsString() const
Returns this argument as a string.
Definition: Diagnostics.h:115
double getAsDouble() const
Returns this argument as a double.
Definition: Diagnostics.h:103
DiagnosticArgument(Attribute attr)
Note: The constructors below are only exposed due to problems accessing constructors from type traits...
Definition: Diagnostics.cpp:35
Type getAsType() const
Returns this argument as a Type.
Definition: Diagnostics.cpp:52
int64_t getAsInteger() const
Returns this argument as a signed integer.
Definition: Diagnostics.h:109
DiagnosticArgumentKind getKind() const
Returns the kind of this argument.
Definition: Diagnostics.h:97
Attribute getAsAttribute() const
Returns this argument as an Attribute.
Definition: Diagnostics.cpp:45
void print(raw_ostream &os) const
Outputs this argument to a stream.
Definition: Diagnostics.cpp:58
uint64_t getAsUnsigned() const
Returns this argument as an unsigned integer.
Definition: Diagnostics.h:124
uint64_t HandlerID
A handle to a specific registered handler object.
Definition: Diagnostics.h:434
InFlightDiagnostic emit(Location loc, DiagnosticSeverity severity)
Create a new inflight diagnostic with the given location and severity.
Definition: Diagnostics.h:460
void eraseHandler(HandlerID id)
Erase the registered diagnostic handler with the given identifier.
llvm::unique_function< LogicalResult(Diagnostic &)> HandlerTy
The handler type for MLIR diagnostics.
Definition: Diagnostics.h:431
HandlerID registerHandler(HandlerTy handler)
Register a new handler for diagnostics to the engine.
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
Definition: Diagnostics.h:155
std::string str() const
Converts the diagnostic to a string.
Diagnostic & attachNote(std::optional< Location > noteLoc=std::nullopt)
Attaches a note to this diagnostic.
MutableArrayRef< DiagnosticArgument > getArguments()
Returns the current list of diagnostic arguments.
Definition: Diagnostics.h:171
std::enable_if_t<!std::is_convertible< Arg, StringRef >::value &&std::is_constructible< DiagnosticArgument, Arg >::value, Diagnostic & > operator<<(Arg &&val)
Stream operator for inserting new diagnostic arguments.
Definition: Diagnostics.h:179
void print(raw_ostream &os) const
Outputs this diagnostic to a stream.
Diagnostic & appendOp(Operation &op, const OpPrintingFlags &flags)
Append an operation with the given printing flags.
An instance of this location represents a tuple of file, line number, and column number.
Definition: Location.h:181
unsigned getLine() const
Definition: Location.cpp:176
StringAttr getFilename() const
Definition: Location.cpp:172
unsigned getColumn() const
Definition: Location.cpp:178
This class represents a diagnostic that is inflight and set to be reported.
Definition: Diagnostics.h:314
void report()
Reports the diagnostic to the engine.
void abandon()
Abandons this diagnostic so that it will no longer be reported.
T findInstanceOf()
Return an instance of the given location type if one is nested under the current location.
Definition: Location.h:44
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:66
MLIRContext is the top-level object for a collection of MLIR operations.
Definition: MLIRContext.h:60
bool shouldPrintStackTraceOnDiagnostic()
Return true if we should attach the current stacktrace to diagnostics when emitted.
DiagnosticEngine & getDiagEngine()
Returns the diagnostic engine for this context.
Set of flags used to control the behavior of the various IR print methods (e.g.
OpPrintingFlags & elideLargeElementsAttrs(int64_t largeElementLimit=16)
Enables the elision of large elements attributes by printing a lexically valid but otherwise meaningl...
Definition: AsmPrinter.cpp:250
OpPrintingFlags & printGenericOpForm(bool enable=true)
Always print operations in the generic form.
Definition: AsmPrinter.cpp:277
OpPrintingFlags & useLocalScope(bool enable=true)
Use local scope when printing the operation.
Definition: AsmPrinter.cpp:297
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
void print(raw_ostream &os, const OpPrintingFlags &flags=std::nullopt)
void eraseOrderIDForThread()
Remove the order id for the current thread.
ParallelDiagnosticHandler(MLIRContext *ctx)
void setOrderIDForThread(size_t orderID)
Set the order id for the current thread.
This diagnostic handler is a simple RAII class that registers and erases a diagnostic handler on a gi...
Definition: Diagnostics.h:522
void setHandler(FuncTy &&handler)
Set the handler to manage via RAII.
Definition: Diagnostics.h:535
This class is a utility diagnostic handler for use with llvm::SourceMgr.
Definition: Diagnostics.h:559
void setCallStackLimit(unsigned limit)
Set the maximum depth that a call stack will be printed. Defaults to 10.
void emitDiagnostic(Location loc, Twine message, DiagnosticSeverity kind, bool displaySourceLine=true)
Emit the given diagnostic information with the held source manager.
raw_ostream & os
The output stream to use when printing diagnostics.
Definition: Diagnostics.h:596
SourceMgrDiagnosticHandler(llvm::SourceMgr &mgr, MLIRContext *ctx, raw_ostream &os, ShouldShowLocFn &&shouldShowLocFn={})
ShouldShowLocFn shouldShowLocFn
A functor used when determining if a location for a diagnostic should be shown.
Definition: Diagnostics.h:600
const llvm::MemoryBuffer * getBufferForFile(StringRef filename)
Get a memory buffer for the given file, or nullptr if no file is available.
llvm::SourceMgr & mgr
The source manager that we are wrapping.
Definition: Diagnostics.h:593
llvm::unique_function< bool(Location)> ShouldShowLocFn
This type represents a functor used to filter out locations when printing a diagnostic.
Definition: Diagnostics.h:568
This class is a utility diagnostic handler for use with llvm::SourceMgr that verifies that emitted di...
Definition: Diagnostics.h:627
LogicalResult verify()
Returns the status of the handler and verifies that all expected diagnostics were emitted.
SourceMgrDiagnosticVerifierHandler(llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out)
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition: Types.h:74
static Type getFromOpaquePointer(const void *pointer)
Definition: Types.h:183
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
void print(raw_ostream &os) const
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition: CallGraph.h:229
AttrTypeReplacer.
Include the generated interface declarations.
InFlightDiagnostic emitWarning(Location loc)
Utility method to emit a warning message using this location.
DiagnosticSeverity
Defines the different supported severity of a diagnostic.
Definition: Diagnostics.h:40
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
InFlightDiagnostic emitRemark(Location loc)
Utility method to emit a remark message using this location.
DiagnosticEngine::HandlerID uniqueHandlerId
This is a unique identifier counter for diagnostic handlers in the context.
llvm::sys::SmartMutex< true > mutex
A mutex to ensure that diagnostics emission is thread-safe.
void emit(Diagnostic &&diag)
Emit a diagnostic using the registered issue handle if present, or with the default behavior if not.
llvm::SmallMapVector< DiagnosticEngine::HandlerID, DiagnosticEngine::HandlerTy, 2 > handlers
These are the handlers used to report diagnostics.
This class represents an expected output diagnostic.
LogicalResult emitError(raw_ostream &os, llvm::SourceMgr &mgr, const Twine &msg)
Emit an error at the location referenced by this diagnostic.
unsigned lineNo
The line number the expected diagnostic should be on.
LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr)
Compute the regex matcher for this diagnostic, using the provided stream and manager to emit diagnost...
bool match(StringRef str) const
Returns true if this diagnostic matches the given string.
bool matched
A flag indicating if the expected diagnostic has been matched yet.
DiagnosticSeverity kind
The severity of the diagnosic expected.
StringRef substring
The substring that is expected to be within the diagnostic.
std::optional< llvm::Regex > substringRegex
An optional regex matcher, if the expected diagnostic sub-string was a regex string.
SMLoc fileLoc
The location of the expected diagnostic within the input file.
ExpectedDiag(DiagnosticSeverity kind, unsigned lineNo, SMLoc fileLoc, StringRef substring)
size_t id
The id for this diagnostic, this is used for ordering.
bool operator<(const ThreadDiagnostic &rhs) const
void print(raw_ostream &os) const override
Dump the current diagnostics that were inflight.
MLIRContext * context
The context to emit the diagnostics to.
void emitDiagnostics(llvm::function_ref< void(Diagnostic &)> emitFn) const
Utility method to emit any held diagnostics.
DiagnosticEngine::HandlerID handlerID
The unique id for the parallel handler.
DenseMap< uint64_t, size_t > threadToOrderID
A mapping between the thread id and the current order id.
void setOrderIDForThread(size_t orderID)
Set the order id for the current thread.
std::vector< ThreadDiagnostic > diagnostics
An unordered list of diagnostics that were emitted.
void eraseOrderIDForThread()
Remove the order id for the current thread.
llvm::sys::SmartMutex< true > mutex
A smart mutex to lock access to the internal state.
llvm::StringMap< unsigned > filenameToBufId
Mapping between file name and buffer ID's.
unsigned getSourceMgrBufferIDForFile(llvm::SourceMgr &mgr, StringRef filename)
Return the SrcManager buffer id for the specified file, or zero if none can be found.
llvm::StringMap< SmallVector< ExpectedDiag, 2 > > expectedDiagsPerFile
A list of expected diagnostics for each buffer of the source manager.
LogicalResult status
The current status of the verifier.
MutableArrayRef< ExpectedDiag > computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf)
Computes the expected diagnostics for the given source buffer.
llvm::Regex expected
Regex to match the expected diagnostics format.
std::optional< MutableArrayRef< ExpectedDiag > > getExpectedDiags(StringRef bufName)
Returns the expected diagnostics for the given source file.