MLIR 24.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
10#include "mlir/IR/Attributes.h"
11#include "mlir/IR/Location.h"
12#include "mlir/IR/MLIRContext.h"
13#include "mlir/IR/Operation.h"
14#include "mlir/IR/Types.h"
15#include "llvm/ADT/MapVector.h"
16#include "llvm/ADT/SmallString.h"
17#include "llvm/ADT/StringMap.h"
18#include "llvm/ADT/TypeSwitch.h"
19#include "llvm/Support/Mutex.h"
20#include "llvm/Support/PrettyStackTrace.h"
21#include "llvm/Support/Regex.h"
22#include "llvm/Support/Signals.h"
23#include "llvm/Support/SourceMgr.h"
24#include "llvm/Support/raw_ostream.h"
25#include <optional>
26
27using namespace mlir;
28using namespace mlir::detail;
29
30//===----------------------------------------------------------------------===//
31// 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.
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'.
87static StringRef
88twineToStrRef(const Twine &val,
89 SmallVectorImpl<std::unique_ptr<char[]>> &strings) {
90 // Allocate memory to hold this string.
91 SmallString<64> data;
92 auto strRef = val.toStringRef(data);
93 if (strRef.empty())
94 return strRef;
95
96 strings.push_back(std::unique_ptr<char[]>(new char[strRef.size()]));
97 memcpy(&strings.back()[0], strRef.data(), strRef.size());
98 // Return a reference to the new string.
99 return StringRef(&strings.back()[0], strRef.size());
100}
101
102/// Stream in a Twine argument.
103Diagnostic &Diagnostic::operator<<(char val) { return *this << Twine(val); }
105 arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings)));
106 return *this;
107}
109 arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings)));
110 return *this;
111}
112
114 arguments.push_back(DiagnosticArgument(val));
115 return *this;
116}
117
118/// Stream in an OperationName.
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/// Adjusts operation printing flags used in diagnostics for the given severity
127/// level.
129 DiagnosticSeverity severity) {
130 flags.useLocalScope();
132 if (severity == DiagnosticSeverity::Error)
133 flags.printGenericOpForm();
134 return flags;
135}
136
137/// Stream in an Operation.
141
145
147 std::string str;
148 llvm::raw_string_ostream os(str);
149 op.print(os, adjustPrintingFlags(flags, severity));
150 // Print on a new line for better readability if the op will be printed on
151 // multiple lines.
152 if (str.find('\n') != std::string::npos)
153 *this << '\n';
154 return *this << str;
155}
156
157/// Stream in a Value.
159 std::string str;
160 llvm::raw_string_ostream os(str);
161 val.print(os, adjustPrintingFlags(OpPrintingFlags(), severity));
162 return *this << str;
163}
164
165/// Outputs this diagnostic to a stream.
167 std::optional<int64_t> messagePartIndex) const {
168 if (!messagePartIndex.has_value()) {
169 for (auto &arg : getArguments())
170 arg.print(os);
171 return;
172 }
173
174 assert(0 <= *messagePartIndex &&
175 *messagePartIndex <= static_cast<int64_t>(messagePartEnds.size()));
176 size_t argumentStart =
177 *messagePartIndex == 0 ? 0 : messagePartEnds[*messagePartIndex - 1];
178 size_t argumentEnd =
179 *messagePartIndex == static_cast<int64_t>(messagePartEnds.size())
180 ? arguments.size()
181 : messagePartEnds[*messagePartIndex];
182 for (auto &arg :
183 getArguments().slice(argumentStart, argumentEnd - argumentStart))
184 arg.print(os);
185}
186
187/// Convert the diagnostic to a string.
188std::string Diagnostic::str() const {
189 std::string str;
190 llvm::raw_string_ostream os(str);
191 print(os);
192 return str;
193}
194
195/// Converts each message part to a separate string.
198 size_t numMessageParts = messagePartEnds.size();
199
200 // Include the current message part if there are no completed parts or if it
201 // contains arguments after the last completed part.
202 if (messagePartEnds.empty() || messagePartEnds.back() != arguments.size())
203 ++numMessageParts;
204 for (size_t i = 0; i < numMessageParts; ++i) {
205 std::string str;
206 llvm::raw_string_ostream os(str);
207 print(os, i);
208 strs.push_back(str);
209 }
210 return strs;
211}
212
213/// Attaches a note to this diagnostic. A new location may be optionally
214/// provided, if not, then the location defaults to the one specified for this
215/// diagnostic. Notes may not be attached to other notes.
216Diagnostic &Diagnostic::attachNote(std::optional<Location> noteLoc) {
217 // We don't allow attaching notes to notes.
218 assert(severity != DiagnosticSeverity::Note &&
219 "cannot attach a note to a note");
220
221 // If a location wasn't provided then reuse our location.
222 if (!noteLoc)
223 noteLoc = loc;
224
225 /// Append and return a new note.
226 notes.push_back(
227 std::make_unique<Diagnostic>(*noteLoc, DiagnosticSeverity::Note));
228 return *notes.back();
229}
230
231/// Allow a diagnostic to be converted to 'failure'.
232Diagnostic::operator LogicalResult() const { return failure(); }
233
234/// Starts a new message part.
236 if (arguments.empty())
237 return;
238 if (!messagePartEnds.empty() && messagePartEnds.back() == arguments.size())
239 return;
240 messagePartEnds.push_back(arguments.size());
241}
242
243//===----------------------------------------------------------------------===//
244// InFlightDiagnostic
245//===----------------------------------------------------------------------===//
246
247/// Allow an inflight diagnostic to be converted to 'failure', otherwise
248/// 'success' if this is an empty diagnostic.
249InFlightDiagnostic::operator LogicalResult() const {
250 return failure(isActive());
251}
252
253/// Reports the diagnostic to the engine.
255 // If this diagnostic is still inflight and it hasn't been abandoned, then
256 // report it.
257 if (isInFlight()) {
258 owner->emit(std::move(*impl));
259 owner = nullptr;
260 }
261 impl.reset();
262}
263
264/// Abandons this diagnostic.
265void InFlightDiagnostic::abandon() { owner = nullptr; }
266
267//===----------------------------------------------------------------------===//
268// DiagnosticEngineImpl
269//===----------------------------------------------------------------------===//
270
271namespace mlir {
272namespace detail {
274 /// Emit a diagnostic using the registered issue handle if present, or with
275 /// the default behavior if not.
276 void emit(Diagnostic &&diag);
277
278 /// A mutex to ensure that diagnostics emission is thread-safe.
280
281 /// These are the handlers used to report diagnostics.
283 2>
285
286 /// This is a unique identifier counter for diagnostic handlers in the
287 /// context. This id starts at 1 to allow for 0 to be used as a sentinel.
289};
290} // namespace detail
291} // namespace mlir
292
293/// Emit a diagnostic using the registered issue handle if present, or with
294/// the default behavior if not.
296 llvm::sys::SmartScopedLock<true> lock(mutex);
297
298 // Try to process the given diagnostic on one of the registered handlers.
299 // Handlers are walked in reverse order, so that the most recent handler is
300 // processed first.
301 for (auto &handlerIt : llvm::reverse(handlers))
302 if (succeeded(handlerIt.second(diag)))
303 return;
304
305 // Otherwise, if this is an error we emit it to stderr.
306 if (diag.getSeverity() != DiagnosticSeverity::Error)
307 return;
308
309 auto &os = llvm::errs();
310 if (!llvm::isa<UnknownLoc>(diag.getLocation()))
311 os << diag.getLocation() << ": ";
312 os << "error: ";
313
314 // The default behavior for errors is to emit them to stderr.
315 os << diag << '\n';
316 os.flush();
317}
318
319//===----------------------------------------------------------------------===//
320// DiagnosticEngine
321//===----------------------------------------------------------------------===//
322
323DiagnosticEngine::DiagnosticEngine() : impl(new DiagnosticEngineImpl()) {}
325
326/// Register a new handler for diagnostics to the engine. This function returns
327/// a unique identifier for the registered handler, which can be used to
328/// unregister this handler at a later time.
330 llvm::sys::SmartScopedLock<true> lock(impl->mutex);
331 auto uniqueID = impl->uniqueHandlerId++;
332 impl->handlers.insert({uniqueID, std::move(handler)});
333 return uniqueID;
334}
335
336/// Erase the registered diagnostic handler with the given identifier.
338 llvm::sys::SmartScopedLock<true> lock(impl->mutex);
339 impl->handlers.erase(handlerID);
340}
341
342/// Emit a diagnostic using the registered issue handler if present, or with
343/// the default behavior if not.
345 assert(diag.getSeverity() != DiagnosticSeverity::Note &&
346 "notes should not be emitted directly");
347 impl->emit(std::move(diag));
348}
349
350/// Helper function used to emit a diagnostic with an optionally empty twine
351/// message. If the message is empty, then it is not inserted into the
352/// diagnostic.
354emitDiag(Location location, DiagnosticSeverity severity, const Twine &message) {
355 MLIRContext *ctx = location->getContext();
356 auto &diagEngine = ctx->getDiagEngine();
357 auto diag = diagEngine.emit(location, severity);
358 if (!message.isTriviallyEmpty())
359 diag << message;
360
361 // Add the stack trace as a note if necessary.
363 std::string bt;
364 {
365 llvm::raw_string_ostream stream(bt);
366 llvm::sys::PrintStackTrace(stream);
367 }
368 if (!bt.empty())
369 diag.attachNote() << "diagnostic emitted with trace:\n" << bt;
370 }
371
372 return diag;
373}
374
375/// Emit an error message using this location.
377InFlightDiagnostic mlir::emitError(Location loc, const Twine &message) {
378 return emitDiag(loc, DiagnosticSeverity::Error, message);
379}
380
381/// Emit a warning message using this location.
386 return emitDiag(loc, DiagnosticSeverity::Warning, message);
387}
388
389/// Emit a remark message using this location.
393InFlightDiagnostic mlir::emitRemark(Location loc, const Twine &message) {
394 return emitDiag(loc, DiagnosticSeverity::Remark, message);
395}
396
397//===----------------------------------------------------------------------===//
398// ScopedDiagnosticHandler
399//===----------------------------------------------------------------------===//
400
402 if (handlerID)
403 ctx->getDiagEngine().eraseHandler(handlerID);
404}
405
406//===----------------------------------------------------------------------===//
407// SourceMgrDiagnosticHandler
408//===----------------------------------------------------------------------===//
409namespace mlir {
410namespace detail {
412 /// Return the SrcManager buffer id for the specified file, or zero if none
413 /// can be found.
414 unsigned getSourceMgrBufferIDForFile(llvm::SourceMgr &mgr,
415 StringRef filename) {
416 // Check for an existing mapping to the buffer id for this file.
417 auto bufferIt = filenameToBufId.find(filename);
418 if (bufferIt != filenameToBufId.end())
419 return bufferIt->second;
420
421 // Look for a buffer in the manager that has this filename.
422 for (unsigned i = 1, e = mgr.getNumBuffers() + 1; i != e; ++i) {
423 auto *buf = mgr.getMemoryBuffer(i);
424 if (buf->getBufferIdentifier() == filename)
425 return filenameToBufId[filename] = i;
426 }
427
428 // Otherwise, try to load the source file.
429 std::string ignored;
430 unsigned id = mgr.AddIncludeFile(std::string(filename), SMLoc(), ignored);
431 filenameToBufId[filename] = id;
432 return id;
433 }
434
435 /// Mapping between file name and buffer ID's.
436 llvm::StringMap<unsigned> filenameToBufId;
437};
438} // namespace detail
439} // namespace mlir
440
441/// Return a processable CallSiteLoc from the given location.
442static std::optional<CallSiteLoc> getCallSiteLoc(Location loc) {
443 if (isa<NameLoc>(loc))
444 return getCallSiteLoc(cast<NameLoc>(loc).getChildLoc());
445 if (auto callLoc = dyn_cast<CallSiteLoc>(loc))
446 return callLoc;
447 if (isa<FusedLoc>(loc)) {
448 for (auto subLoc : cast<FusedLoc>(loc).getLocations()) {
449 if (auto callLoc = getCallSiteLoc(subLoc)) {
450 return callLoc;
451 }
452 }
453 return std::nullopt;
454 }
455 return std::nullopt;
456}
457
458/// Given a diagnostic kind, returns the LLVM DiagKind.
459static llvm::SourceMgr::DiagKind getDiagKind(DiagnosticSeverity kind) {
460 switch (kind) {
462 return llvm::SourceMgr::DK_Note;
464 return llvm::SourceMgr::DK_Warning;
466 return llvm::SourceMgr::DK_Error;
468 return llvm::SourceMgr::DK_Remark;
469 }
470 llvm_unreachable("Unknown DiagnosticSeverity");
471}
472
481
486
488
491 bool displaySourceLine) {
492 // Extract a file location from this loc.
493 auto fileLoc = loc->findInstanceOf<FileLineColLoc>();
494
495 // If one doesn't exist, then print the raw message without a source location.
496 if (!fileLoc) {
497 std::string str;
498 llvm::raw_string_ostream strOS(str);
499 if (!llvm::isa<UnknownLoc>(loc))
500 strOS << loc << ": ";
501 strOS << message;
502 return mgr.PrintMessage(os, SMLoc(), getDiagKind(kind), str);
503 }
504
505 // Otherwise if we are displaying the source line, try to convert the file
506 // location to an SMLoc.
507 if (displaySourceLine) {
508 auto smloc = convertLocToSMLoc(fileLoc);
509 if (smloc.isValid())
510 return mgr.PrintMessage(os, smloc, getDiagKind(kind), message);
511 }
512
513 // If the conversion was unsuccessful, create a diagnostic with the file
514 // information. We manually combine the line and column to avoid asserts in
515 // the constructor of SMDiagnostic that takes a location.
516 std::string locStr;
517 llvm::raw_string_ostream locOS(locStr);
518 locOS << fileLoc.getFilename().getValue() << ":" << fileLoc.getLine() << ":"
519 << fileLoc.getColumn();
520 llvm::SMDiagnostic diag(locStr, getDiagKind(kind), message.str());
521 diag.print(nullptr, os);
522}
523
524/// Emit the given diagnostic with the held source manager.
527 auto addLocToStack = [&](Location loc, StringRef locContext) {
528 if (std::optional<Location> showableLoc = findLocToShow(loc))
529 locationStack.emplace_back(*showableLoc, locContext);
530 };
531
532 // Add locations to display for this diagnostic.
533 Location loc = diag.getLocation();
534 addLocToStack(loc, /*locContext=*/{});
535
536 // If the diagnostic location was a call site location, add the call stack as
537 // well.
538 if (auto callLoc = getCallSiteLoc(loc)) {
539 // Print the call stack while valid, or until the limit is reached.
540 loc = callLoc->getCaller();
541 for (unsigned curDepth = 0; curDepth < callStackLimit; ++curDepth) {
542 addLocToStack(loc, "called from");
543 if ((callLoc = getCallSiteLoc(loc)))
544 loc = callLoc->getCaller();
545 else
546 break;
547 }
548 }
549
550 // If the location stack is empty, use the initial location.
551 if (locationStack.empty()) {
552 for (const std::string &str : diag.strs())
553 emitDiagnostic(diag.getLocation(), str, diag.getSeverity());
554
555 // Otherwise, use the location stack.
556 } else {
557 for (const std::string &str : diag.strs())
558 emitDiagnostic(locationStack.front().first, str, diag.getSeverity());
559 for (auto &it : llvm::drop_begin(locationStack))
560 emitDiagnostic(it.first, it.second, DiagnosticSeverity::Note);
561 }
562
563 // Emit each of the notes. Only display the source code if the location is
564 // different from the previous location.
565 for (auto &note : diag.getNotes()) {
566 emitDiagnostic(note.getLocation(), note.str(), note.getSeverity(),
567 /*displaySourceLine=*/loc != note.getLocation());
568 loc = note.getLocation();
569 }
570}
571
573 callStackLimit = limit;
574}
575
576/// Get a memory buffer for the given file, or nullptr if one is not found.
577const llvm::MemoryBuffer *
579 if (unsigned id = impl->getSourceMgrBufferIDForFile(mgr, filename))
580 return mgr.getMemoryBuffer(id);
581 return nullptr;
582}
583
584std::optional<Location>
585SourceMgrDiagnosticHandler::findLocToShow(Location loc) {
586 if (!shouldShowLocFn)
587 return loc;
588 if (!shouldShowLocFn(loc))
589 return std::nullopt;
590
591 // Recurse into the child locations of some of location types.
593 .Case([&](CallSiteLoc callLoc) -> std::optional<Location> {
594 // We recurse into the callee of a call site, as the caller will be
595 // emitted in a different note on the main diagnostic.
596 return findLocToShow(callLoc.getCallee());
597 })
598 .Case([&](FileLineColLoc) -> std::optional<Location> { return loc; })
599 .Case([&](FusedLoc fusedLoc) -> std::optional<Location> {
600 // Fused location is unique in that we try to find a sub-location to
601 // show, rather than the top-level location itself.
602 for (Location childLoc : fusedLoc.getLocations())
603 if (std::optional<Location> showableLoc = findLocToShow(childLoc))
604 return showableLoc;
605 return std::nullopt;
606 })
607 .Case([&](NameLoc nameLoc) -> std::optional<Location> {
608 return findLocToShow(nameLoc.getChildLoc());
609 })
610 .Case([&](OpaqueLoc opaqueLoc) -> std::optional<Location> {
611 // OpaqueLoc always falls back to a different source location.
612 return findLocToShow(opaqueLoc.getFallbackLocation());
613 })
614 .Case([](UnknownLoc) -> std::optional<Location> {
615 // Prefer not to show unknown locations.
616 return std::nullopt;
617 });
618}
619
620/// Get a memory buffer for the given file, or the main file of the source
621/// manager if one doesn't exist. This always returns non-null.
622SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) {
623 // The column and line may be zero to represent unknown column and/or unknown
624 /// line/column information.
625 if (loc.getLine() == 0 || loc.getColumn() == 0)
626 return SMLoc();
627
628 unsigned bufferId = impl->getSourceMgrBufferIDForFile(mgr, loc.getFilename());
629 if (!bufferId)
630 return SMLoc();
631 return mgr.FindLocForLineAndColumn(bufferId, loc.getLine(), loc.getColumn());
632}
633
634//===----------------------------------------------------------------------===//
635// SourceMgrDiagnosticVerifierHandler
636//===----------------------------------------------------------------------===//
637
638namespace mlir {
639namespace detail {
640/// This class represents an expected output diagnostic.
645
646 /// Emit an error at the location referenced by this diagnostic.
647 LogicalResult emitError(raw_ostream &os, llvm::SourceMgr &mgr,
648 const Twine &msg) {
649 // fileLoc may be invalid when the expected diagnostic used an unknown
650 // location specifier (e.g. `// expected-error @unknown {{...}}`). In that
651 // case, skip the source range to avoid a null-pointer dereference and an
652 // assertion in SMRange that both endpoints must have the same validity.
653 if (fileLoc.isValid()) {
654 SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
655 substring.size()));
656 mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg, range);
657 } else {
658 mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg);
659 }
660 return failure();
661 }
662
663 /// Returns true if this diagnostic matches the given string.
664 bool match(StringRef str) const {
665 // If this isn't a regex diagnostic, we simply check if the string was
666 // contained.
667 if (substringRegex)
668 return substringRegex->match(str);
669 return str.contains(substring);
670 }
671
672 /// Compute the regex matcher for this diagnostic, using the provided stream
673 /// and manager to emit diagnostics as necessary.
674 LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr) {
675 std::string regexStr;
676 llvm::raw_string_ostream regexOS(regexStr);
677 StringRef strToProcess = substring;
678 while (!strToProcess.empty()) {
679 // Find the next regex block.
680 size_t regexIt = strToProcess.find("{{");
681 if (regexIt == StringRef::npos) {
682 regexOS << llvm::Regex::escape(strToProcess);
683 break;
684 }
685 regexOS << llvm::Regex::escape(strToProcess.take_front(regexIt));
686 strToProcess = strToProcess.drop_front(regexIt + 2);
687
688 // Find the end of the regex block.
689 size_t regexEndIt = strToProcess.find("}}");
690 if (regexEndIt == StringRef::npos)
691 return emitError(os, mgr, "found start of regex with no end '}}'");
692 StringRef regexStr = strToProcess.take_front(regexEndIt);
693
694 // Validate that the regex is actually valid.
695 std::string regexError;
696 if (!llvm::Regex(regexStr).isValid(regexError))
697 return emitError(os, mgr, "invalid regex: " + regexError);
698
699 regexOS << '(' << regexStr << ')';
700 strToProcess = strToProcess.drop_front(regexEndIt + 2);
701 }
702 substringRegex = llvm::Regex(regexStr);
703 return success();
704 }
705
706 /// The severity of the diagnosic expected.
708 /// The line number the expected diagnostic should be on.
709 unsigned lineNo;
710 /// The location of the expected diagnostic within the input file.
711 SMLoc fileLoc;
712 /// A flag indicating if the expected diagnostic has been matched yet.
713 bool matched = false;
714 /// The substring that is expected to be within the diagnostic.
715 StringRef substring;
716 /// An optional regex matcher, if the expected diagnostic sub-string was a
717 /// regex string.
718 std::optional<llvm::Regex> substringRegex;
719};
720
725
726 /// Returns the expected diagnostics for the given source file.
727 std::optional<MutableArrayRef<ExpectedDiag>>
728 getExpectedDiags(StringRef bufName);
729
730 /// Computes the expected diagnostics for the given source buffer.
732 computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,
733 const llvm::MemoryBuffer *buf);
734
738
739 /// The current status of the verifier.
740 LogicalResult status;
741
742 /// A list of expected diagnostics for each buffer of the source manager.
743 llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
744
745 /// A list of expected diagnostics with unknown locations.
747
748 /// Regex to match the expected diagnostics format.
749 llvm::Regex expected =
750 llvm::Regex("expected-(error|note|remark|warning)(-re)? "
751 "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$");
752
753 /// Verification level.
756};
757} // namespace detail
758} // namespace mlir
759
760/// Given a diagnostic kind, return a human readable string for it.
761static StringRef getDiagKindStr(DiagnosticSeverity kind) {
762 switch (kind) {
764 return "note";
766 return "warning";
768 return "error";
770 return "remark";
771 }
772 llvm_unreachable("Unknown DiagnosticSeverity");
773}
774
775std::optional<MutableArrayRef<ExpectedDiag>>
777 auto expectedDiags = expectedDiagsPerFile.find(bufName);
778 if (expectedDiags != expectedDiagsPerFile.end())
779 return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
780 return std::nullopt;
781}
782
785 raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {
786 // If the buffer is invalid, return an empty list.
787 if (!buf)
788 return {};
789 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
790
791 // The number of the last line that did not correlate to a designator.
792 unsigned lastNonDesignatorLine = 0;
793
794 // The indices of designators that apply to the next non designator line.
795 SmallVector<unsigned, 1> designatorsForNextLine;
796
797 // Scan the file for expected-* designators.
799 buf->getBuffer().split(lines, '\n');
800 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
802 if (!expected.match(lines[lineNo].rtrim(), &matches)) {
803 // Check for designators that apply to this line.
804 if (!designatorsForNextLine.empty()) {
805 for (unsigned diagIndex : designatorsForNextLine)
806 expectedDiags[diagIndex].lineNo = lineNo + 1;
807 designatorsForNextLine.clear();
808 }
809 lastNonDesignatorLine = lineNo;
810 continue;
811 }
812
813 // Point to the start of expected-*.
814 SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
815
817 if (matches[1] == "error")
819 else if (matches[1] == "warning")
821 else if (matches[1] == "remark")
823 else {
824 assert(matches[1] == "note");
826 }
827 ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
828
829 // Check to see if this is a regex match, i.e. it includes the `-re`.
830 if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {
831 status = failure();
832 continue;
833 }
834
835 StringRef offsetMatch = matches[3];
836 if (!offsetMatch.empty()) {
837 offsetMatch = offsetMatch.drop_front(1);
838
839 // Get the integer value without the @ and +/- prefix.
840 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
841 int offset;
842 offsetMatch.drop_front().getAsInteger(0, offset);
843
844 if (offsetMatch.front() == '+')
845 record.lineNo += offset;
846 else
847 record.lineNo -= offset;
848 } else if (offsetMatch.consume_front("unknown")) {
849 // This is matching unknown locations.
850 record.fileLoc = SMLoc();
851 expectedUnknownLocDiags.emplace_back(std::move(record));
852 continue;
853 } else if (offsetMatch.consume_front("above")) {
854 // If the designator applies 'above' we add it to the last non
855 // designator line.
856 record.lineNo = lastNonDesignatorLine + 1;
857 } else {
858 // Otherwise, this is a 'below' designator and applies to the next
859 // non-designator line.
860 assert(offsetMatch.consume_front("below"));
861 designatorsForNextLine.push_back(expectedDiags.size());
862
863 // Set the line number to the last in the case that this designator ends
864 // up dangling.
865 record.lineNo = e;
866 }
867 }
868 expectedDiags.emplace_back(std::move(record));
869 }
870 return expectedDiags;
871}
872
874 llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level)
875 : SourceMgrDiagnosticHandler(srcMgr, ctx, out),
877 // Compute the expected diagnostics for each of the current files in the
878 // source manager.
879 for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)
880 (void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));
881
882 // The base class registered a handler that prints every diagnostic. The
883 // verifier takes its place: it consumes diagnostics and reports unexpected
884 // ones itself.
885 setHandler([this](Diagnostic &diag) { process(diag); });
886}
887
889 llvm::SourceMgr &srcMgr, MLIRContext *ctx, Level level)
890 : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs(), level) {}
891
893 // Ensure that all expected diagnostics were handled.
894 (void)verify();
895}
896
897/// Returns the status of the verifier and verifies that all expected
898/// diagnostics were emitted. This return success if all diagnostics were
899/// verified correctly, failure otherwise.
901 // Verify that all expected errors were seen.
902 auto checkExpectedDiags = [&](ExpectedDiag &err) {
903 if (!err.matched)
904 impl->status =
905 err.emitError(os, mgr,
906 "expected " + getDiagKindStr(err.kind) + " \"" +
907 err.substring + "\" was not produced");
908 };
909 for (auto &expectedDiagsPair : impl->expectedDiagsPerFile)
910 for (auto &err : expectedDiagsPair.second)
911 checkExpectedDiags(err);
912 for (auto &err : impl->expectedUnknownLocDiags)
913 checkExpectedDiags(err);
914 impl->expectedDiagsPerFile.clear();
915 return impl->status;
916}
917
918std::unique_ptr<ScopedDiagnosticHandler>
920 return std::make_unique<ScopedDiagnosticHandler>(
921 ctx, [this](Diagnostic &diag) { process(diag); });
922}
923
924/// Process a diagnostic and its notes.
925void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) {
926 for (const std::string &str : diag.strs())
927 process(diag.getLocation(), str, diag.getSeverity());
928 for (auto &note : diag.getNotes())
929 process(note);
930}
931
932/// Process a diagnostic at a certain location.
933void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,
934 StringRef msg,
935 DiagnosticSeverity kind) {
938
939 if (fileLoc) {
940 // Get the expected diagnostics for this file.
941 if (auto maybeDiags = impl->getExpectedDiags(fileLoc.getFilename())) {
942 diags = *maybeDiags;
943 } else {
944 diags = impl->computeExpectedDiags(
945 os, mgr, getBufferForFile(fileLoc.getFilename()));
946 }
947 } else {
948 // Get all expected diagnostics at unknown locations.
949 diags = impl->expectedUnknownLocDiags;
950 }
951
952 // Search for a matching expected diagnostic.
953 // If we find something that is close then emit a more specific error.
954 ExpectedDiag *nearMiss = nullptr;
955
956 // If this was an expected error, remember that we saw it and return.
957 for (auto &e : diags) {
958 // File line must match (unless it's an unknown location).
959 if (fileLoc && fileLoc.getLine() != e.lineNo)
960 continue;
961 if (e.match(msg)) {
962 if (e.kind == kind) {
963 e.matched = true;
964 return;
965 }
966
967 // If this only differs based on the diagnostic kind, then consider it
968 // to be a near miss.
969 nearMiss = &e;
970 }
971 }
972
973 if (impl->getVerifyLevel() == Level::OnlyExpected)
974 return;
975
976 // Otherwise, emit an error for the near miss.
977 if (nearMiss)
978 mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error,
979 "'" + getDiagKindStr(kind) +
980 "' diagnostic emitted when expecting a '" +
981 getDiagKindStr(nearMiss->kind) + "'");
982 else
983 emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
985 impl->status = failure();
986}
987
988//===----------------------------------------------------------------------===//
989// ParallelDiagnosticHandler
990//===----------------------------------------------------------------------===//
991
992namespace mlir {
993namespace detail {
994struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry {
997 : id(id), diag(std::move(diag)) {}
998 bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; }
999
1000 /// The id for this diagnostic, this is used for ordering.
1001 /// Note: This id corresponds to the ordered position of the current element
1002 /// being processed by a given thread.
1003 size_t id;
1004
1005 /// The diagnostic.
1007 };
1008
1010 handlerID = ctx->getDiagEngine().registerHandler([this](Diagnostic &diag) {
1011 uint64_t tid = llvm::get_threadid();
1012 llvm::sys::SmartScopedLock<true> lock(mutex);
1013
1014 // If this thread is not tracked, then return failure to let another
1015 // handler process this diagnostic.
1016 if (!threadToOrderID.count(tid))
1017 return failure();
1018
1019 // Append a new diagnostic.
1020 diagnostics.emplace_back(threadToOrderID[tid], std::move(diag));
1021 return success();
1022 });
1023 }
1024
1026 // Erase this handler from the context.
1027 context->getDiagEngine().eraseHandler(handlerID);
1028
1029 // Early exit if there are no diagnostics, this is the common case.
1030 if (diagnostics.empty())
1031 return;
1032
1033 // Emit the diagnostics back to the context.
1035 return context->getDiagEngine().emit(std::move(diag));
1036 });
1037 }
1038
1039 /// Utility method to emit any held diagnostics.
1040 void emitDiagnostics(llvm::function_ref<void(Diagnostic &)> emitFn) const {
1041 // Stable sort all of the diagnostics that were emitted. This creates a
1042 // deterministic ordering for the diagnostics based upon which order id they
1043 // were emitted for.
1044 llvm::stable_sort(diagnostics);
1045
1046 // Emit each diagnostic to the context again.
1048 emitFn(diag.diag);
1049 }
1050
1051 /// Set the order id for the current thread.
1052 void setOrderIDForThread(size_t orderID) {
1053 uint64_t tid = llvm::get_threadid();
1054 llvm::sys::SmartScopedLock<true> lock(mutex);
1055 threadToOrderID[tid] = orderID;
1056 }
1057
1058 /// Remove the order id for the current thread.
1060 uint64_t tid = llvm::get_threadid();
1061 llvm::sys::SmartScopedLock<true> lock(mutex);
1062 threadToOrderID.erase(tid);
1063 }
1064
1065 /// Dump the current diagnostics that were inflight.
1066 void print(raw_ostream &os) const override {
1067 // Early exit if there are no diagnostics, this is the common case.
1068 if (diagnostics.empty())
1069 return;
1070
1071 os << "In-Flight Diagnostics:\n";
1072 emitDiagnostics([&](const Diagnostic &diag) {
1073 os.indent(4);
1074
1075 // Print each diagnostic with the format:
1076 // "<location>: <kind>: <msg>"
1077 if (!llvm::isa<UnknownLoc>(diag.getLocation()))
1078 os << diag.getLocation() << ": ";
1079 switch (diag.getSeverity()) {
1081 os << "error: ";
1082 break;
1084 os << "warning: ";
1085 break;
1087 os << "note: ";
1088 break;
1090 os << "remark: ";
1091 break;
1092 }
1093 os << diag << '\n';
1094 });
1095 }
1096
1097 /// A smart mutex to lock access to the internal state.
1099
1100 /// A mapping between the thread id and the current order id.
1102
1103 /// An unordered list of diagnostics that were emitted.
1104 mutable std::vector<ThreadDiagnostic> diagnostics;
1105
1106 /// The unique id for the parallel handler.
1108
1109 /// The context to emit the diagnostics to.
1111};
1112} // namespace detail
1113} // namespace mlir
1114
1118
1119/// Set the order id for the current thread.
1121 impl->setOrderIDForThread(orderID);
1122}
1123
1124/// Remove the order id for the current thread. This removes the thread from
1125/// diagnostics tracking.
1127 impl->eraseOrderIDForThread();
1128}
return success()
static OpPrintingFlags adjustPrintingFlags(OpPrintingFlags flags, DiagnosticSeverity severity)
Adjusts operation printing flags used in diagnostics for the given severity level.
static StringRef twineToStrRef(const Twine &val, SmallVectorImpl< std::unique_ptr< char[]> > &strings)
Convert a Twine to a StringRef.
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 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.
MLIRContext * getContext() const
Return the context this attribute belongs to.
static Attribute getFromOpaquePointer(const void *ptr)
Construct an attribute from the opaque pointer representation.
Definition Attributes.h:75
A variant type that holds a single argument for a diagnostic.
Definition Diagnostics.h:53
DiagnosticArgumentKind
Enum that represents the different kinds of diagnostic arguments supported.
Definition Diagnostics.h:85
StringRef getAsString() const
Returns this argument as a string.
double getAsDouble() const
Returns this argument as a double.
DiagnosticArgument(Attribute attr)
Note: The constructors below are only exposed due to problems accessing constructors from type traits...
Type getAsType() const
Returns this argument as a Type.
int64_t getAsInteger() const
Returns this argument as a signed integer.
DiagnosticArgumentKind getKind() const
Returns the kind of this argument.
Definition Diagnostics.h:98
Attribute getAsAttribute() const
Returns this argument as an Attribute.
void print(raw_ostream &os) const
Outputs this argument to a stream.
uint64_t getAsUnsigned() const
Returns this argument as an unsigned integer.
uint64_t HandlerID
A handle to a specific registered handler object.
InFlightDiagnostic emit(Location loc, DiagnosticSeverity severity)
Create a new inflight diagnostic with the given location and severity.
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.
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.
std::string str() const
Converts the diagnostic to a string, concatenating all the parts if there are multiples.
void startNewMessagePart()
Starts a new message part.
SmallVector< std::string > strs() const
Converts each message part to a separate 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.
Diagnostic(Location loc, DiagnosticSeverity severity)
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.
void print(raw_ostream &os, std::optional< int64_t > messagePartIndex=std::nullopt) 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:174
unsigned getLine() const
Definition Location.cpp:173
StringAttr getFilename() const
Definition Location.cpp:169
unsigned getColumn() const
Definition Location.cpp:175
This class represents a diagnostic that is inflight and set to be reported.
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:32
T findInstanceOf()
Return an instance of the given location type if one is nested under the current location.
Definition Location.h:45
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
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...
OpPrintingFlags & printGenericOpForm(bool enable=true)
Always print operations in the generic form.
OpPrintingFlags & useLocalScope(bool enable=true)
Use local scope when printing the operation.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1169
Operation * getOperation() const
Definition Operation.h:1175
OpPrintingFlags & flags()
Definition Operation.h:1173
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
void print(raw_ostream &os, const OpPrintingFlags &flags={})
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.
ScopedDiagnosticHandler(MLIRContext *ctx)
void setHandler(FuncTy &&handler)
Set the handler to manage via RAII.
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.
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.
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.
llvm::unique_function< bool(Location)> ShouldShowLocFn
This type represents a functor used to filter out locations when printing a diagnostic.
SourceMgrDiagnosticVerifierHandler(llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level=Level::All)
std::unique_ptr< ScopedDiagnosticHandler > registerInContext(MLIRContext *ctx)
Register this handler with ctx and return a scoped registration.
LogicalResult verify()
Returns the status of the handler and verifies that all expected diagnostics were emitted.
static Type getFromOpaquePointer(const void *pointer)
Definition Types.h:170
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:227
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:41
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.
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
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.
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.