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 
666  : status(success()), level(level) {}
667 
668  /// Returns the expected diagnostics for the given source file.
669  std::optional<MutableArrayRef<ExpectedDiag>>
670  getExpectedDiags(StringRef bufName);
671 
672  /// Computes the expected diagnostics for the given source buffer.
674  computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,
675  const llvm::MemoryBuffer *buf);
676 
678  return level;
679  }
680 
681  /// The current status of the verifier.
682  LogicalResult status;
683 
684  /// A list of expected diagnostics for each buffer of the source manager.
685  llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
686 
687  /// A list of expected diagnostics with unknown locations.
689 
690  /// Regex to match the expected diagnostics format.
691  llvm::Regex expected =
692  llvm::Regex("expected-(error|note|remark|warning)(-re)? "
693  "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$");
694 
695  /// Verification level.
698 };
699 } // namespace detail
700 } // namespace mlir
701 
702 /// Given a diagnostic kind, return a human readable string for it.
704  switch (kind) {
706  return "note";
708  return "warning";
710  return "error";
712  return "remark";
713  }
714  llvm_unreachable("Unknown DiagnosticSeverity");
715 }
716 
717 std::optional<MutableArrayRef<ExpectedDiag>>
718 SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) {
719  auto expectedDiags = expectedDiagsPerFile.find(bufName);
720  if (expectedDiags != expectedDiagsPerFile.end())
721  return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
722  return std::nullopt;
723 }
724 
726 SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
727  raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {
728  // If the buffer is invalid, return an empty list.
729  if (!buf)
730  return std::nullopt;
731  auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
732 
733  // The number of the last line that did not correlate to a designator.
734  unsigned lastNonDesignatorLine = 0;
735 
736  // The indices of designators that apply to the next non designator line.
737  SmallVector<unsigned, 1> designatorsForNextLine;
738 
739  // Scan the file for expected-* designators.
741  buf->getBuffer().split(lines, '\n');
742  for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
744  if (!expected.match(lines[lineNo].rtrim(), &matches)) {
745  // Check for designators that apply to this line.
746  if (!designatorsForNextLine.empty()) {
747  for (unsigned diagIndex : designatorsForNextLine)
748  expectedDiags[diagIndex].lineNo = lineNo + 1;
749  designatorsForNextLine.clear();
750  }
751  lastNonDesignatorLine = lineNo;
752  continue;
753  }
754 
755  // Point to the start of expected-*.
756  SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
757 
759  if (matches[1] == "error")
761  else if (matches[1] == "warning")
763  else if (matches[1] == "remark")
765  else {
766  assert(matches[1] == "note");
768  }
769  ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
770 
771  // Check to see if this is a regex match, i.e. it includes the `-re`.
772  if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {
773  status = failure();
774  continue;
775  }
776 
777  StringRef offsetMatch = matches[3];
778  if (!offsetMatch.empty()) {
779  offsetMatch = offsetMatch.drop_front(1);
780 
781  // Get the integer value without the @ and +/- prefix.
782  if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
783  int offset;
784  offsetMatch.drop_front().getAsInteger(0, offset);
785 
786  if (offsetMatch.front() == '+')
787  record.lineNo += offset;
788  else
789  record.lineNo -= offset;
790  } else if (offsetMatch.consume_front("unknown")) {
791  // This is matching unknown locations.
792  record.fileLoc = SMLoc();
793  expectedUnknownLocDiags.emplace_back(std::move(record));
794  continue;
795  } else if (offsetMatch.consume_front("above")) {
796  // If the designator applies 'above' we add it to the last non
797  // designator line.
798  record.lineNo = lastNonDesignatorLine + 1;
799  } else {
800  // Otherwise, this is a 'below' designator and applies to the next
801  // non-designator line.
802  assert(offsetMatch.consume_front("below"));
803  designatorsForNextLine.push_back(expectedDiags.size());
804 
805  // Set the line number to the last in the case that this designator ends
806  // up dangling.
807  record.lineNo = e;
808  }
809  }
810  expectedDiags.emplace_back(std::move(record));
811  }
812  return expectedDiags;
813 }
814 
816  llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level)
817  : SourceMgrDiagnosticHandler(srcMgr, ctx, out),
819  // Compute the expected diagnostics for each of the current files in the
820  // source manager.
821  for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)
822  (void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));
823 
824  // Register a handler to verify the diagnostics.
825  setHandler([&](Diagnostic &diag) {
826  // Process the main diagnostics.
827  process(diag);
828 
829  // Process each of the notes.
830  for (auto &note : diag.getNotes())
831  process(note);
832  });
833 }
834 
836  llvm::SourceMgr &srcMgr, MLIRContext *ctx, Level level)
837  : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs(), level) {}
838 
840  // Ensure that all expected diagnostics were handled.
841  (void)verify();
842 }
843 
844 /// Returns the status of the verifier and verifies that all expected
845 /// diagnostics were emitted. This return success if all diagnostics were
846 /// verified correctly, failure otherwise.
848  // Verify that all expected errors were seen.
849  auto checkExpectedDiags = [&](ExpectedDiag &err) {
850  if (!err.matched)
851  impl->status =
852  err.emitError(os, mgr,
853  "expected " + getDiagKindStr(err.kind) + " \"" +
854  err.substring + "\" was not produced");
855  };
856  for (auto &expectedDiagsPair : impl->expectedDiagsPerFile)
857  for (auto &err : expectedDiagsPair.second)
858  checkExpectedDiags(err);
859  for (auto &err : impl->expectedUnknownLocDiags)
860  checkExpectedDiags(err);
861  impl->expectedDiagsPerFile.clear();
862  return impl->status;
863 }
864 
865 /// Process a single diagnostic.
866 void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) {
867  return process(diag.getLocation(), diag.str(), diag.getSeverity());
868 }
869 
870 /// Process a diagnostic at a certain location.
871 void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,
872  StringRef msg,
876 
877  if (fileLoc) {
878  // Get the expected diagnostics for this file.
879  if (auto maybeDiags = impl->getExpectedDiags(fileLoc.getFilename())) {
880  diags = *maybeDiags;
881  } else {
882  diags = impl->computeExpectedDiags(
883  os, mgr, getBufferForFile(fileLoc.getFilename()));
884  }
885  } else {
886  // Get all expected diagnostics at unknown locations.
887  diags = impl->expectedUnknownLocDiags;
888  }
889 
890  // Search for a matching expected diagnostic.
891  // If we find something that is close then emit a more specific error.
892  ExpectedDiag *nearMiss = nullptr;
893 
894  // If this was an expected error, remember that we saw it and return.
895  for (auto &e : diags) {
896  // File line must match (unless it's an unknown location).
897  if (fileLoc && fileLoc.getLine() != e.lineNo)
898  continue;
899  if (e.match(msg)) {
900  if (e.kind == kind) {
901  e.matched = true;
902  return;
903  }
904 
905  // If this only differs based on the diagnostic kind, then consider it
906  // to be a near miss.
907  nearMiss = &e;
908  }
909  }
910 
911  if (impl->getVerifyLevel() == Level::OnlyExpected)
912  return;
913 
914  // Otherwise, emit an error for the near miss.
915  if (nearMiss)
916  mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error,
917  "'" + getDiagKindStr(kind) +
918  "' diagnostic emitted when expecting a '" +
919  getDiagKindStr(nearMiss->kind) + "'");
920  else
921  emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
923  impl->status = failure();
924 }
925 
926 //===----------------------------------------------------------------------===//
927 // ParallelDiagnosticHandler
928 //===----------------------------------------------------------------------===//
929 
930 namespace mlir {
931 namespace detail {
932 struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry {
935  : id(id), diag(std::move(diag)) {}
936  bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; }
937 
938  /// The id for this diagnostic, this is used for ordering.
939  /// Note: This id corresponds to the ordered position of the current element
940  /// being processed by a given thread.
941  size_t id;
942 
943  /// The diagnostic.
945  };
946 
949  uint64_t tid = llvm::get_threadid();
950  llvm::sys::SmartScopedLock<true> lock(mutex);
951 
952  // If this thread is not tracked, then return failure to let another
953  // handler process this diagnostic.
954  if (!threadToOrderID.count(tid))
955  return failure();
956 
957  // Append a new diagnostic.
958  diagnostics.emplace_back(threadToOrderID[tid], std::move(diag));
959  return success();
960  });
961  }
962 
964  // Erase this handler from the context.
966 
967  // Early exit if there are no diagnostics, this is the common case.
968  if (diagnostics.empty())
969  return;
970 
971  // Emit the diagnostics back to the context.
973  return context->getDiagEngine().emit(std::move(diag));
974  });
975  }
976 
977  /// Utility method to emit any held diagnostics.
978  void emitDiagnostics(llvm::function_ref<void(Diagnostic &)> emitFn) const {
979  // Stable sort all of the diagnostics that were emitted. This creates a
980  // deterministic ordering for the diagnostics based upon which order id they
981  // were emitted for.
982  std::stable_sort(diagnostics.begin(), diagnostics.end());
983 
984  // Emit each diagnostic to the context again.
986  emitFn(diag.diag);
987  }
988 
989  /// Set the order id for the current thread.
990  void setOrderIDForThread(size_t orderID) {
991  uint64_t tid = llvm::get_threadid();
992  llvm::sys::SmartScopedLock<true> lock(mutex);
993  threadToOrderID[tid] = orderID;
994  }
995 
996  /// Remove the order id for the current thread.
998  uint64_t tid = llvm::get_threadid();
999  llvm::sys::SmartScopedLock<true> lock(mutex);
1000  threadToOrderID.erase(tid);
1001  }
1002 
1003  /// Dump the current diagnostics that were inflight.
1004  void print(raw_ostream &os) const override {
1005  // Early exit if there are no diagnostics, this is the common case.
1006  if (diagnostics.empty())
1007  return;
1008 
1009  os << "In-Flight Diagnostics:\n";
1010  emitDiagnostics([&](const Diagnostic &diag) {
1011  os.indent(4);
1012 
1013  // Print each diagnostic with the format:
1014  // "<location>: <kind>: <msg>"
1015  if (!llvm::isa<UnknownLoc>(diag.getLocation()))
1016  os << diag.getLocation() << ": ";
1017  switch (diag.getSeverity()) {
1018  case DiagnosticSeverity::Error:
1019  os << "error: ";
1020  break;
1021  case DiagnosticSeverity::Warning:
1022  os << "warning: ";
1023  break;
1024  case DiagnosticSeverity::Note:
1025  os << "note: ";
1026  break;
1027  case DiagnosticSeverity::Remark:
1028  os << "remark: ";
1029  break;
1030  }
1031  os << diag << '\n';
1032  });
1033  }
1034 
1035  /// A smart mutex to lock access to the internal state.
1037 
1038  /// A mapping between the thread id and the current order id.
1040 
1041  /// An unordered list of diagnostics that were emitted.
1042  mutable std::vector<ThreadDiagnostic> diagnostics;
1043 
1044  /// The unique id for the parallel handler.
1046 
1047  /// The context to emit the diagnostics to.
1049 };
1050 } // namespace detail
1051 } // namespace mlir
1052 
1054  : impl(new ParallelDiagnosticHandlerImpl(ctx)) {}
1056 
1057 /// Set the order id for the current thread.
1059  impl->setOrderIDForThread(orderID);
1060 }
1061 
1062 /// Remove the order id for the current thread. This removes the thread from
1063 /// diagnostics tracking.
1065  impl->eraseOrderIDForThread();
1066 }
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::@1182::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.
Location objects represent source locations information in MLIR.
Definition: Location.h:31
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
SourceMgrDiagnosticVerifierHandler(llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level=Level::All)
LogicalResult verify()
Returns the status of the handler and verifies that all expected diagnostics were emitted.
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.
SourceMgrDiagnosticVerifierHandler::Level getVerifyLevel() const
llvm::Regex expected
Regex to match the expected diagnostics format.
SourceMgrDiagnosticVerifierHandler::Level level
Verification level.
SmallVector< ExpectedDiag, 2 > expectedUnknownLocDiags
A list of expected diagnostics with unknown locations.
SourceMgrDiagnosticVerifierHandlerImpl(SourceMgrDiagnosticVerifierHandler::Level level)
std::optional< MutableArrayRef< ExpectedDiag > > getExpectedDiags(StringRef bufName)
Returns the expected diagnostics for the given source file.