MLIR 22.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 twineToStrRef(const Twine &val,
88 std::vector<std::unique_ptr<char[]>> &strings) {
89 // Allocate memory to hold this string.
90 SmallString<64> data;
91 auto strRef = val.toStringRef(data);
92 if (strRef.empty())
93 return strRef;
94
95 strings.push_back(std::unique_ptr<char[]>(new char[strRef.size()]));
96 memcpy(&strings.back()[0], strRef.data(), strRef.size());
97 // Return a reference to the new string.
98 return StringRef(&strings.back()[0], strRef.size());
99}
100
101/// Stream in a Twine argument.
102Diagnostic &Diagnostic::operator<<(char val) { return *this << Twine(val); }
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();
131 if (severity == DiagnosticSeverity::Error)
132 flags.printGenericOpForm();
133 return flags;
134}
135
136/// Stream in an Operation.
140
144
146 std::string str;
147 llvm::raw_string_ostream os(str);
148 op.print(os, adjustPrintingFlags(flags, severity));
149 // Print on a new line for better readability if the op will be printed on
150 // multiple lines.
151 if (str.find('\n') != std::string::npos)
152 *this << '\n';
153 return *this << str;
154}
155
156/// Stream in a Value.
158 std::string str;
159 llvm::raw_string_ostream os(str);
160 val.print(os, adjustPrintingFlags(OpPrintingFlags(), severity));
161 return *this << str;
162}
163
164/// Outputs this diagnostic to a stream.
166 for (auto &arg : getArguments())
167 arg.print(os);
168}
169
170/// Convert the diagnostic to a string.
171std::string Diagnostic::str() const {
172 std::string str;
173 llvm::raw_string_ostream os(str);
174 print(os);
175 return str;
176}
177
178/// Attaches a note to this diagnostic. A new location may be optionally
179/// provided, if not, then the location defaults to the one specified for this
180/// diagnostic. Notes may not be attached to other notes.
181Diagnostic &Diagnostic::attachNote(std::optional<Location> noteLoc) {
182 // We don't allow attaching notes to notes.
183 assert(severity != DiagnosticSeverity::Note &&
184 "cannot attach a note to a note");
185
186 // If a location wasn't provided then reuse our location.
187 if (!noteLoc)
188 noteLoc = loc;
189
190 /// Append and return a new note.
191 notes.push_back(
192 std::make_unique<Diagnostic>(*noteLoc, DiagnosticSeverity::Note));
193 return *notes.back();
194}
195
196/// Allow a diagnostic to be converted to 'failure'.
197Diagnostic::operator LogicalResult() const { return failure(); }
198
199//===----------------------------------------------------------------------===//
200// InFlightDiagnostic
201//===----------------------------------------------------------------------===//
202
203/// Allow an inflight diagnostic to be converted to 'failure', otherwise
204/// 'success' if this is an empty diagnostic.
205InFlightDiagnostic::operator LogicalResult() const {
206 return failure(isActive());
207}
208
209/// Reports the diagnostic to the engine.
211 // If this diagnostic is still inflight and it hasn't been abandoned, then
212 // report it.
213 if (isInFlight()) {
214 owner->emit(std::move(*impl));
215 owner = nullptr;
216 }
217 impl.reset();
218}
219
220/// Abandons this diagnostic.
221void InFlightDiagnostic::abandon() { owner = nullptr; }
222
223//===----------------------------------------------------------------------===//
224// DiagnosticEngineImpl
225//===----------------------------------------------------------------------===//
226
227namespace mlir {
228namespace detail {
230 /// Emit a diagnostic using the registered issue handle if present, or with
231 /// the default behavior if not.
232 void emit(Diagnostic &&diag);
233
234 /// A mutex to ensure that diagnostics emission is thread-safe.
236
237 /// These are the handlers used to report diagnostics.
239 2>
241
242 /// This is a unique identifier counter for diagnostic handlers in the
243 /// context. This id starts at 1 to allow for 0 to be used as a sentinel.
245};
246} // namespace detail
247} // namespace mlir
248
249/// Emit a diagnostic using the registered issue handle if present, or with
250/// the default behavior if not.
252 llvm::sys::SmartScopedLock<true> lock(mutex);
253
254 // Try to process the given diagnostic on one of the registered handlers.
255 // Handlers are walked in reverse order, so that the most recent handler is
256 // processed first.
257 for (auto &handlerIt : llvm::reverse(handlers))
258 if (succeeded(handlerIt.second(diag)))
259 return;
260
261 // Otherwise, if this is an error we emit it to stderr.
262 if (diag.getSeverity() != DiagnosticSeverity::Error)
263 return;
264
265 auto &os = llvm::errs();
266 if (!llvm::isa<UnknownLoc>(diag.getLocation()))
267 os << diag.getLocation() << ": ";
268 os << "error: ";
269
270 // The default behavior for errors is to emit them to stderr.
271 os << diag << '\n';
272 os.flush();
273}
274
275//===----------------------------------------------------------------------===//
276// DiagnosticEngine
277//===----------------------------------------------------------------------===//
278
279DiagnosticEngine::DiagnosticEngine() : impl(new DiagnosticEngineImpl()) {}
281
282/// Register a new handler for diagnostics to the engine. This function returns
283/// a unique identifier for the registered handler, which can be used to
284/// unregister this handler at a later time.
286 llvm::sys::SmartScopedLock<true> lock(impl->mutex);
287 auto uniqueID = impl->uniqueHandlerId++;
288 impl->handlers.insert({uniqueID, std::move(handler)});
289 return uniqueID;
290}
291
292/// Erase the registered diagnostic handler with the given identifier.
294 llvm::sys::SmartScopedLock<true> lock(impl->mutex);
295 impl->handlers.erase(handlerID);
296}
297
298/// Emit a diagnostic using the registered issue handler if present, or with
299/// the default behavior if not.
301 assert(diag.getSeverity() != DiagnosticSeverity::Note &&
302 "notes should not be emitted directly");
303 impl->emit(std::move(diag));
304}
305
306/// Helper function used to emit a diagnostic with an optionally empty twine
307/// message. If the message is empty, then it is not inserted into the
308/// diagnostic.
310emitDiag(Location location, DiagnosticSeverity severity, const Twine &message) {
311 MLIRContext *ctx = location->getContext();
312 auto &diagEngine = ctx->getDiagEngine();
313 auto diag = diagEngine.emit(location, severity);
314 if (!message.isTriviallyEmpty())
315 diag << message;
316
317 // Add the stack trace as a note if necessary.
319 std::string bt;
320 {
321 llvm::raw_string_ostream stream(bt);
322 llvm::sys::PrintStackTrace(stream);
323 }
324 if (!bt.empty())
325 diag.attachNote() << "diagnostic emitted with trace:\n" << bt;
326 }
327
328 return diag;
329}
330
331/// Emit an error message using this location.
333InFlightDiagnostic mlir::emitError(Location loc, const Twine &message) {
334 return emitDiag(loc, DiagnosticSeverity::Error, message);
335}
336
337/// Emit a warning message using this location.
342 return emitDiag(loc, DiagnosticSeverity::Warning, message);
343}
344
345/// Emit a remark message using this location.
349InFlightDiagnostic mlir::emitRemark(Location loc, const Twine &message) {
350 return emitDiag(loc, DiagnosticSeverity::Remark, message);
351}
352
353//===----------------------------------------------------------------------===//
354// ScopedDiagnosticHandler
355//===----------------------------------------------------------------------===//
356
358 if (handlerID)
359 ctx->getDiagEngine().eraseHandler(handlerID);
360}
361
362//===----------------------------------------------------------------------===//
363// SourceMgrDiagnosticHandler
364//===----------------------------------------------------------------------===//
365namespace mlir {
366namespace detail {
368 /// Return the SrcManager buffer id for the specified file, or zero if none
369 /// can be found.
370 unsigned getSourceMgrBufferIDForFile(llvm::SourceMgr &mgr,
371 StringRef filename) {
372 // Check for an existing mapping to the buffer id for this file.
373 auto bufferIt = filenameToBufId.find(filename);
374 if (bufferIt != filenameToBufId.end())
375 return bufferIt->second;
376
377 // Look for a buffer in the manager that has this filename.
378 for (unsigned i = 1, e = mgr.getNumBuffers() + 1; i != e; ++i) {
379 auto *buf = mgr.getMemoryBuffer(i);
380 if (buf->getBufferIdentifier() == filename)
381 return filenameToBufId[filename] = i;
382 }
383
384 // Otherwise, try to load the source file.
385 std::string ignored;
386 unsigned id = mgr.AddIncludeFile(std::string(filename), SMLoc(), ignored);
387 filenameToBufId[filename] = id;
388 return id;
389 }
390
391 /// Mapping between file name and buffer ID's.
392 llvm::StringMap<unsigned> filenameToBufId;
393};
394} // namespace detail
395} // namespace mlir
396
397/// Return a processable CallSiteLoc from the given location.
398static std::optional<CallSiteLoc> getCallSiteLoc(Location loc) {
399 if (isa<NameLoc>(loc))
400 return getCallSiteLoc(cast<NameLoc>(loc).getChildLoc());
401 if (auto callLoc = dyn_cast<CallSiteLoc>(loc))
402 return callLoc;
403 if (isa<FusedLoc>(loc)) {
404 for (auto subLoc : cast<FusedLoc>(loc).getLocations()) {
405 if (auto callLoc = getCallSiteLoc(subLoc)) {
406 return callLoc;
407 }
408 }
409 return std::nullopt;
410 }
411 return std::nullopt;
412}
413
414/// Given a diagnostic kind, returns the LLVM DiagKind.
415static llvm::SourceMgr::DiagKind getDiagKind(DiagnosticSeverity kind) {
416 switch (kind) {
418 return llvm::SourceMgr::DK_Note;
420 return llvm::SourceMgr::DK_Warning;
422 return llvm::SourceMgr::DK_Error;
424 return llvm::SourceMgr::DK_Remark;
425 }
426 llvm_unreachable("Unknown DiagnosticSeverity");
427}
428
437
442
444
447 bool displaySourceLine) {
448 // Extract a file location from this loc.
449 auto fileLoc = loc->findInstanceOf<FileLineColLoc>();
450
451 // If one doesn't exist, then print the raw message without a source location.
452 if (!fileLoc) {
453 std::string str;
454 llvm::raw_string_ostream strOS(str);
455 if (!llvm::isa<UnknownLoc>(loc))
456 strOS << loc << ": ";
457 strOS << message;
458 return mgr.PrintMessage(os, SMLoc(), getDiagKind(kind), str);
459 }
460
461 // Otherwise if we are displaying the source line, try to convert the file
462 // location to an SMLoc.
463 if (displaySourceLine) {
464 auto smloc = convertLocToSMLoc(fileLoc);
465 if (smloc.isValid())
466 return mgr.PrintMessage(os, smloc, getDiagKind(kind), message);
467 }
468
469 // If the conversion was unsuccessful, create a diagnostic with the file
470 // information. We manually combine the line and column to avoid asserts in
471 // the constructor of SMDiagnostic that takes a location.
472 std::string locStr;
473 llvm::raw_string_ostream locOS(locStr);
474 locOS << fileLoc.getFilename().getValue() << ":" << fileLoc.getLine() << ":"
475 << fileLoc.getColumn();
476 llvm::SMDiagnostic diag(locStr, getDiagKind(kind), message.str());
477 diag.print(nullptr, os);
478}
479
480/// Emit the given diagnostic with the held source manager.
483 auto addLocToStack = [&](Location loc, StringRef locContext) {
484 if (std::optional<Location> showableLoc = findLocToShow(loc))
485 locationStack.emplace_back(*showableLoc, locContext);
486 };
487
488 // Add locations to display for this diagnostic.
489 Location loc = diag.getLocation();
490 addLocToStack(loc, /*locContext=*/{});
491
492 // If the diagnostic location was a call site location, add the call stack as
493 // well.
494 if (auto callLoc = getCallSiteLoc(loc)) {
495 // Print the call stack while valid, or until the limit is reached.
496 loc = callLoc->getCaller();
497 for (unsigned curDepth = 0; curDepth < callStackLimit; ++curDepth) {
498 addLocToStack(loc, "called from");
499 if ((callLoc = getCallSiteLoc(loc)))
500 loc = callLoc->getCaller();
501 else
502 break;
503 }
504 }
505
506 // If the location stack is empty, use the initial location.
507 if (locationStack.empty()) {
508 emitDiagnostic(diag.getLocation(), diag.str(), diag.getSeverity());
509
510 // Otherwise, use the location stack.
511 } else {
512 emitDiagnostic(locationStack.front().first, diag.str(), diag.getSeverity());
513 for (auto &it : llvm::drop_begin(locationStack))
514 emitDiagnostic(it.first, it.second, DiagnosticSeverity::Note);
515 }
516
517 // Emit each of the notes. Only display the source code if the location is
518 // different from the previous location.
519 for (auto &note : diag.getNotes()) {
520 emitDiagnostic(note.getLocation(), note.str(), note.getSeverity(),
521 /*displaySourceLine=*/loc != note.getLocation());
522 loc = note.getLocation();
523 }
524}
525
527 callStackLimit = limit;
528}
529
530/// Get a memory buffer for the given file, or nullptr if one is not found.
531const llvm::MemoryBuffer *
533 if (unsigned id = impl->getSourceMgrBufferIDForFile(mgr, filename))
534 return mgr.getMemoryBuffer(id);
535 return nullptr;
536}
537
538std::optional<Location>
539SourceMgrDiagnosticHandler::findLocToShow(Location loc) {
540 if (!shouldShowLocFn)
541 return loc;
542 if (!shouldShowLocFn(loc))
543 return std::nullopt;
544
545 // Recurse into the child locations of some of location types.
547 .Case([&](CallSiteLoc callLoc) -> std::optional<Location> {
548 // We recurse into the callee of a call site, as the caller will be
549 // emitted in a different note on the main diagnostic.
550 return findLocToShow(callLoc.getCallee());
551 })
552 .Case([&](FileLineColLoc) -> std::optional<Location> { return loc; })
553 .Case([&](FusedLoc fusedLoc) -> std::optional<Location> {
554 // Fused location is unique in that we try to find a sub-location to
555 // show, rather than the top-level location itself.
556 for (Location childLoc : fusedLoc.getLocations())
557 if (std::optional<Location> showableLoc = findLocToShow(childLoc))
558 return showableLoc;
559 return std::nullopt;
560 })
561 .Case([&](NameLoc nameLoc) -> std::optional<Location> {
562 return findLocToShow(nameLoc.getChildLoc());
563 })
564 .Case([&](OpaqueLoc opaqueLoc) -> std::optional<Location> {
565 // OpaqueLoc always falls back to a different source location.
566 return findLocToShow(opaqueLoc.getFallbackLocation());
567 })
568 .Case([](UnknownLoc) -> std::optional<Location> {
569 // Prefer not to show unknown locations.
570 return std::nullopt;
571 });
572}
573
574/// Get a memory buffer for the given file, or the main file of the source
575/// manager if one doesn't exist. This always returns non-null.
576SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) {
577 // The column and line may be zero to represent unknown column and/or unknown
578 /// line/column information.
579 if (loc.getLine() == 0 || loc.getColumn() == 0)
580 return SMLoc();
581
582 unsigned bufferId = impl->getSourceMgrBufferIDForFile(mgr, loc.getFilename());
583 if (!bufferId)
584 return SMLoc();
585 return mgr.FindLocForLineAndColumn(bufferId, loc.getLine(), loc.getColumn());
586}
587
588//===----------------------------------------------------------------------===//
589// SourceMgrDiagnosticVerifierHandler
590//===----------------------------------------------------------------------===//
591
592namespace mlir {
593namespace detail {
594/// This class represents an expected output diagnostic.
599
600 /// Emit an error at the location referenced by this diagnostic.
601 LogicalResult emitError(raw_ostream &os, llvm::SourceMgr &mgr,
602 const Twine &msg) {
603 SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
604 substring.size()));
605 mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg, range);
606 return failure();
607 }
608
609 /// Returns true if this diagnostic matches the given string.
610 bool match(StringRef str) const {
611 // If this isn't a regex diagnostic, we simply check if the string was
612 // contained.
613 if (substringRegex)
614 return substringRegex->match(str);
615 return str.contains(substring);
616 }
617
618 /// Compute the regex matcher for this diagnostic, using the provided stream
619 /// and manager to emit diagnostics as necessary.
620 LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr) {
621 std::string regexStr;
622 llvm::raw_string_ostream regexOS(regexStr);
623 StringRef strToProcess = substring;
624 while (!strToProcess.empty()) {
625 // Find the next regex block.
626 size_t regexIt = strToProcess.find("{{");
627 if (regexIt == StringRef::npos) {
628 regexOS << llvm::Regex::escape(strToProcess);
629 break;
630 }
631 regexOS << llvm::Regex::escape(strToProcess.take_front(regexIt));
632 strToProcess = strToProcess.drop_front(regexIt + 2);
633
634 // Find the end of the regex block.
635 size_t regexEndIt = strToProcess.find("}}");
636 if (regexEndIt == StringRef::npos)
637 return emitError(os, mgr, "found start of regex with no end '}}'");
638 StringRef regexStr = strToProcess.take_front(regexEndIt);
639
640 // Validate that the regex is actually valid.
641 std::string regexError;
642 if (!llvm::Regex(regexStr).isValid(regexError))
643 return emitError(os, mgr, "invalid regex: " + regexError);
644
645 regexOS << '(' << regexStr << ')';
646 strToProcess = strToProcess.drop_front(regexEndIt + 2);
647 }
648 substringRegex = llvm::Regex(regexStr);
649 return success();
650 }
651
652 /// The severity of the diagnosic expected.
654 /// The line number the expected diagnostic should be on.
655 unsigned lineNo;
656 /// The location of the expected diagnostic within the input file.
657 SMLoc fileLoc;
658 /// A flag indicating if the expected diagnostic has been matched yet.
659 bool matched = false;
660 /// The substring that is expected to be within the diagnostic.
661 StringRef substring;
662 /// An optional regex matcher, if the expected diagnostic sub-string was a
663 /// regex string.
664 std::optional<llvm::Regex> substringRegex;
665};
666
671
672 /// Returns the expected diagnostics for the given source file.
673 std::optional<MutableArrayRef<ExpectedDiag>>
674 getExpectedDiags(StringRef bufName);
675
676 /// Computes the expected diagnostics for the given source buffer.
678 computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,
679 const llvm::MemoryBuffer *buf);
680
684
685 /// The current status of the verifier.
686 LogicalResult status;
687
688 /// A list of expected diagnostics for each buffer of the source manager.
689 llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
690
691 /// A list of expected diagnostics with unknown locations.
693
694 /// Regex to match the expected diagnostics format.
695 llvm::Regex expected =
696 llvm::Regex("expected-(error|note|remark|warning)(-re)? "
697 "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$");
698
699 /// Verification level.
702};
703} // namespace detail
704} // namespace mlir
705
706/// Given a diagnostic kind, return a human readable string for it.
707static StringRef getDiagKindStr(DiagnosticSeverity kind) {
708 switch (kind) {
710 return "note";
712 return "warning";
714 return "error";
716 return "remark";
717 }
718 llvm_unreachable("Unknown DiagnosticSeverity");
719}
720
721std::optional<MutableArrayRef<ExpectedDiag>>
723 auto expectedDiags = expectedDiagsPerFile.find(bufName);
724 if (expectedDiags != expectedDiagsPerFile.end())
725 return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
726 return std::nullopt;
727}
728
731 raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {
732 // If the buffer is invalid, return an empty list.
733 if (!buf)
734 return {};
735 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
736
737 // The number of the last line that did not correlate to a designator.
738 unsigned lastNonDesignatorLine = 0;
739
740 // The indices of designators that apply to the next non designator line.
741 SmallVector<unsigned, 1> designatorsForNextLine;
742
743 // Scan the file for expected-* designators.
745 buf->getBuffer().split(lines, '\n');
746 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
748 if (!expected.match(lines[lineNo].rtrim(), &matches)) {
749 // Check for designators that apply to this line.
750 if (!designatorsForNextLine.empty()) {
751 for (unsigned diagIndex : designatorsForNextLine)
752 expectedDiags[diagIndex].lineNo = lineNo + 1;
753 designatorsForNextLine.clear();
754 }
755 lastNonDesignatorLine = lineNo;
756 continue;
757 }
758
759 // Point to the start of expected-*.
760 SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
761
763 if (matches[1] == "error")
765 else if (matches[1] == "warning")
767 else if (matches[1] == "remark")
769 else {
770 assert(matches[1] == "note");
772 }
773 ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
774
775 // Check to see if this is a regex match, i.e. it includes the `-re`.
776 if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {
777 status = failure();
778 continue;
779 }
780
781 StringRef offsetMatch = matches[3];
782 if (!offsetMatch.empty()) {
783 offsetMatch = offsetMatch.drop_front(1);
784
785 // Get the integer value without the @ and +/- prefix.
786 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
787 int offset;
788 offsetMatch.drop_front().getAsInteger(0, offset);
789
790 if (offsetMatch.front() == '+')
791 record.lineNo += offset;
792 else
793 record.lineNo -= offset;
794 } else if (offsetMatch.consume_front("unknown")) {
795 // This is matching unknown locations.
796 record.fileLoc = SMLoc();
797 expectedUnknownLocDiags.emplace_back(std::move(record));
798 continue;
799 } else if (offsetMatch.consume_front("above")) {
800 // If the designator applies 'above' we add it to the last non
801 // designator line.
802 record.lineNo = lastNonDesignatorLine + 1;
803 } else {
804 // Otherwise, this is a 'below' designator and applies to the next
805 // non-designator line.
806 assert(offsetMatch.consume_front("below"));
807 designatorsForNextLine.push_back(expectedDiags.size());
808
809 // Set the line number to the last in the case that this designator ends
810 // up dangling.
811 record.lineNo = e;
812 }
813 }
814 expectedDiags.emplace_back(std::move(record));
815 }
816 return expectedDiags;
817}
818
820 llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level)
821 : SourceMgrDiagnosticHandler(srcMgr, ctx, out),
823 // Compute the expected diagnostics for each of the current files in the
824 // source manager.
825 for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)
826 (void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));
827
829}
830
832 llvm::SourceMgr &srcMgr, MLIRContext *ctx, Level level)
833 : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs(), level) {}
834
836 // Ensure that all expected diagnostics were handled.
837 (void)verify();
838}
839
840/// Returns the status of the verifier and verifies that all expected
841/// diagnostics were emitted. This return success if all diagnostics were
842/// verified correctly, failure otherwise.
844 // Verify that all expected errors were seen.
845 auto checkExpectedDiags = [&](ExpectedDiag &err) {
846 if (!err.matched)
847 impl->status =
848 err.emitError(os, mgr,
849 "expected " + getDiagKindStr(err.kind) + " \"" +
850 err.substring + "\" was not produced");
851 };
852 for (auto &expectedDiagsPair : impl->expectedDiagsPerFile)
853 for (auto &err : expectedDiagsPair.second)
854 checkExpectedDiags(err);
855 for (auto &err : impl->expectedUnknownLocDiags)
856 checkExpectedDiags(err);
857 impl->expectedDiagsPerFile.clear();
858 return impl->status;
859}
860
862 ctx->getDiagEngine().registerHandler([&](Diagnostic &diag) {
863 // Process the main diagnostics.
864 process(diag);
865
866 // Process each of the notes.
867 for (auto &note : diag.getNotes())
868 process(note);
869 });
870}
871
872/// Process a single diagnostic.
873void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) {
874 return process(diag.getLocation(), diag.str(), diag.getSeverity());
875}
876
877/// Process a diagnostic at a certain location.
878void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,
879 StringRef msg,
880 DiagnosticSeverity kind) {
883
884 if (fileLoc) {
885 // Get the expected diagnostics for this file.
886 if (auto maybeDiags = impl->getExpectedDiags(fileLoc.getFilename())) {
887 diags = *maybeDiags;
888 } else {
889 diags = impl->computeExpectedDiags(
890 os, mgr, getBufferForFile(fileLoc.getFilename()));
891 }
892 } else {
893 // Get all expected diagnostics at unknown locations.
894 diags = impl->expectedUnknownLocDiags;
895 }
896
897 // Search for a matching expected diagnostic.
898 // If we find something that is close then emit a more specific error.
899 ExpectedDiag *nearMiss = nullptr;
900
901 // If this was an expected error, remember that we saw it and return.
902 for (auto &e : diags) {
903 // File line must match (unless it's an unknown location).
904 if (fileLoc && fileLoc.getLine() != e.lineNo)
905 continue;
906 if (e.match(msg)) {
907 if (e.kind == kind) {
908 e.matched = true;
909 return;
910 }
911
912 // If this only differs based on the diagnostic kind, then consider it
913 // to be a near miss.
914 nearMiss = &e;
915 }
916 }
917
918 if (impl->getVerifyLevel() == Level::OnlyExpected)
919 return;
920
921 // Otherwise, emit an error for the near miss.
922 if (nearMiss)
923 mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error,
924 "'" + getDiagKindStr(kind) +
925 "' diagnostic emitted when expecting a '" +
926 getDiagKindStr(nearMiss->kind) + "'");
927 else
928 emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
930 impl->status = failure();
931}
932
933//===----------------------------------------------------------------------===//
934// ParallelDiagnosticHandler
935//===----------------------------------------------------------------------===//
936
937namespace mlir {
938namespace detail {
939struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry {
942 : id(id), diag(std::move(diag)) {}
943 bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; }
944
945 /// The id for this diagnostic, this is used for ordering.
946 /// Note: This id corresponds to the ordered position of the current element
947 /// being processed by a given thread.
948 size_t id;
949
950 /// The diagnostic.
952 };
953
955 handlerID = ctx->getDiagEngine().registerHandler([this](Diagnostic &diag) {
956 uint64_t tid = llvm::get_threadid();
957 llvm::sys::SmartScopedLock<true> lock(mutex);
958
959 // If this thread is not tracked, then return failure to let another
960 // handler process this diagnostic.
961 if (!threadToOrderID.count(tid))
962 return failure();
963
964 // Append a new diagnostic.
965 diagnostics.emplace_back(threadToOrderID[tid], std::move(diag));
966 return success();
967 });
968 }
969
971 // Erase this handler from the context.
972 context->getDiagEngine().eraseHandler(handlerID);
973
974 // Early exit if there are no diagnostics, this is the common case.
975 if (diagnostics.empty())
976 return;
977
978 // Emit the diagnostics back to the context.
980 return context->getDiagEngine().emit(std::move(diag));
981 });
982 }
983
984 /// Utility method to emit any held diagnostics.
985 void emitDiagnostics(llvm::function_ref<void(Diagnostic &)> emitFn) const {
986 // Stable sort all of the diagnostics that were emitted. This creates a
987 // deterministic ordering for the diagnostics based upon which order id they
988 // were emitted for.
989 llvm::stable_sort(diagnostics);
990
991 // Emit each diagnostic to the context again.
993 emitFn(diag.diag);
994 }
995
996 /// Set the order id for the current thread.
997 void setOrderIDForThread(size_t orderID) {
998 uint64_t tid = llvm::get_threadid();
999 llvm::sys::SmartScopedLock<true> lock(mutex);
1000 threadToOrderID[tid] = orderID;
1001 }
1002
1003 /// Remove the order id for the current thread.
1005 uint64_t tid = llvm::get_threadid();
1006 llvm::sys::SmartScopedLock<true> lock(mutex);
1007 threadToOrderID.erase(tid);
1008 }
1009
1010 /// Dump the current diagnostics that were inflight.
1011 void print(raw_ostream &os) const override {
1012 // Early exit if there are no diagnostics, this is the common case.
1013 if (diagnostics.empty())
1014 return;
1015
1016 os << "In-Flight Diagnostics:\n";
1017 emitDiagnostics([&](const Diagnostic &diag) {
1018 os.indent(4);
1019
1020 // Print each diagnostic with the format:
1021 // "<location>: <kind>: <msg>"
1022 if (!llvm::isa<UnknownLoc>(diag.getLocation()))
1023 os << diag.getLocation() << ": ";
1024 switch (diag.getSeverity()) {
1026 os << "error: ";
1027 break;
1029 os << "warning: ";
1030 break;
1032 os << "note: ";
1033 break;
1035 os << "remark: ";
1036 break;
1037 }
1038 os << diag << '\n';
1039 });
1040 }
1041
1042 /// A smart mutex to lock access to the internal state.
1044
1045 /// A mapping between the thread id and the current order id.
1047
1048 /// An unordered list of diagnostics that were emitted.
1049 mutable std::vector<ThreadDiagnostic> diagnostics;
1050
1051 /// The unique id for the parallel handler.
1053
1054 /// The context to emit the diagnostics to.
1056};
1057} // namespace detail
1058} // namespace mlir
1059
1063
1064/// Set the order id for the current thread.
1066 impl->setOrderIDForThread(orderID);
1067}
1068
1069/// Remove the order id for the current thread. This removes the thread from
1070/// diagnostics tracking.
1072 impl->eraseOrderIDForThread();
1073}
return success()
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.
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.
static std::string diag(const llvm::Value &value)
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.
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) 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:1111
Operation * getOperation() const
Definition Operation.h:1117
OpPrintingFlags & flags()
Definition Operation.h:1115
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={})
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)
void registerInContext(MLIRContext *ctx)
Register this handler with the given context.
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:167
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: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:144
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:126
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.