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