MLIR 23.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 // fileLoc may be invalid when the expected diagnostic used an unknown
604 // location specifier (e.g. `// expected-error @unknown {{...}}`). In that
605 // case, skip the source range to avoid a null-pointer dereference and an
606 // assertion in SMRange that both endpoints must have the same validity.
607 if (fileLoc.isValid()) {
608 SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
609 substring.size()));
610 mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg, range);
611 } else {
612 mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg);
613 }
614 return failure();
615 }
616
617 /// Returns true if this diagnostic matches the given string.
618 bool match(StringRef str) const {
619 // If this isn't a regex diagnostic, we simply check if the string was
620 // contained.
621 if (substringRegex)
622 return substringRegex->match(str);
623 return str.contains(substring);
624 }
625
626 /// Compute the regex matcher for this diagnostic, using the provided stream
627 /// and manager to emit diagnostics as necessary.
628 LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr) {
629 std::string regexStr;
630 llvm::raw_string_ostream regexOS(regexStr);
631 StringRef strToProcess = substring;
632 while (!strToProcess.empty()) {
633 // Find the next regex block.
634 size_t regexIt = strToProcess.find("{{");
635 if (regexIt == StringRef::npos) {
636 regexOS << llvm::Regex::escape(strToProcess);
637 break;
638 }
639 regexOS << llvm::Regex::escape(strToProcess.take_front(regexIt));
640 strToProcess = strToProcess.drop_front(regexIt + 2);
641
642 // Find the end of the regex block.
643 size_t regexEndIt = strToProcess.find("}}");
644 if (regexEndIt == StringRef::npos)
645 return emitError(os, mgr, "found start of regex with no end '}}'");
646 StringRef regexStr = strToProcess.take_front(regexEndIt);
647
648 // Validate that the regex is actually valid.
649 std::string regexError;
650 if (!llvm::Regex(regexStr).isValid(regexError))
651 return emitError(os, mgr, "invalid regex: " + regexError);
652
653 regexOS << '(' << regexStr << ')';
654 strToProcess = strToProcess.drop_front(regexEndIt + 2);
655 }
656 substringRegex = llvm::Regex(regexStr);
657 return success();
658 }
659
660 /// The severity of the diagnosic expected.
662 /// The line number the expected diagnostic should be on.
663 unsigned lineNo;
664 /// The location of the expected diagnostic within the input file.
665 SMLoc fileLoc;
666 /// A flag indicating if the expected diagnostic has been matched yet.
667 bool matched = false;
668 /// The substring that is expected to be within the diagnostic.
669 StringRef substring;
670 /// An optional regex matcher, if the expected diagnostic sub-string was a
671 /// regex string.
672 std::optional<llvm::Regex> substringRegex;
673};
674
679
680 /// Returns the expected diagnostics for the given source file.
681 std::optional<MutableArrayRef<ExpectedDiag>>
682 getExpectedDiags(StringRef bufName);
683
684 /// Computes the expected diagnostics for the given source buffer.
686 computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,
687 const llvm::MemoryBuffer *buf);
688
692
693 /// The current status of the verifier.
694 LogicalResult status;
695
696 /// A list of expected diagnostics for each buffer of the source manager.
697 llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
698
699 /// A list of expected diagnostics with unknown locations.
701
702 /// Regex to match the expected diagnostics format.
703 llvm::Regex expected =
704 llvm::Regex("expected-(error|note|remark|warning)(-re)? "
705 "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$");
706
707 /// Verification level.
710};
711} // namespace detail
712} // namespace mlir
713
714/// Given a diagnostic kind, return a human readable string for it.
715static StringRef getDiagKindStr(DiagnosticSeverity kind) {
716 switch (kind) {
718 return "note";
720 return "warning";
722 return "error";
724 return "remark";
725 }
726 llvm_unreachable("Unknown DiagnosticSeverity");
727}
728
729std::optional<MutableArrayRef<ExpectedDiag>>
731 auto expectedDiags = expectedDiagsPerFile.find(bufName);
732 if (expectedDiags != expectedDiagsPerFile.end())
733 return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
734 return std::nullopt;
735}
736
739 raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {
740 // If the buffer is invalid, return an empty list.
741 if (!buf)
742 return {};
743 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
744
745 // The number of the last line that did not correlate to a designator.
746 unsigned lastNonDesignatorLine = 0;
747
748 // The indices of designators that apply to the next non designator line.
749 SmallVector<unsigned, 1> designatorsForNextLine;
750
751 // Scan the file for expected-* designators.
753 buf->getBuffer().split(lines, '\n');
754 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
756 if (!expected.match(lines[lineNo].rtrim(), &matches)) {
757 // Check for designators that apply to this line.
758 if (!designatorsForNextLine.empty()) {
759 for (unsigned diagIndex : designatorsForNextLine)
760 expectedDiags[diagIndex].lineNo = lineNo + 1;
761 designatorsForNextLine.clear();
762 }
763 lastNonDesignatorLine = lineNo;
764 continue;
765 }
766
767 // Point to the start of expected-*.
768 SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
769
771 if (matches[1] == "error")
773 else if (matches[1] == "warning")
775 else if (matches[1] == "remark")
777 else {
778 assert(matches[1] == "note");
780 }
781 ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
782
783 // Check to see if this is a regex match, i.e. it includes the `-re`.
784 if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {
785 status = failure();
786 continue;
787 }
788
789 StringRef offsetMatch = matches[3];
790 if (!offsetMatch.empty()) {
791 offsetMatch = offsetMatch.drop_front(1);
792
793 // Get the integer value without the @ and +/- prefix.
794 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
795 int offset;
796 offsetMatch.drop_front().getAsInteger(0, offset);
797
798 if (offsetMatch.front() == '+')
799 record.lineNo += offset;
800 else
801 record.lineNo -= offset;
802 } else if (offsetMatch.consume_front("unknown")) {
803 // This is matching unknown locations.
804 record.fileLoc = SMLoc();
805 expectedUnknownLocDiags.emplace_back(std::move(record));
806 continue;
807 } else if (offsetMatch.consume_front("above")) {
808 // If the designator applies 'above' we add it to the last non
809 // designator line.
810 record.lineNo = lastNonDesignatorLine + 1;
811 } else {
812 // Otherwise, this is a 'below' designator and applies to the next
813 // non-designator line.
814 assert(offsetMatch.consume_front("below"));
815 designatorsForNextLine.push_back(expectedDiags.size());
816
817 // Set the line number to the last in the case that this designator ends
818 // up dangling.
819 record.lineNo = e;
820 }
821 }
822 expectedDiags.emplace_back(std::move(record));
823 }
824 return expectedDiags;
825}
826
828 llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level)
829 : SourceMgrDiagnosticHandler(srcMgr, ctx, out),
831 // Compute the expected diagnostics for each of the current files in the
832 // source manager.
833 for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)
834 (void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));
835
837}
838
840 llvm::SourceMgr &srcMgr, MLIRContext *ctx, Level level)
841 : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs(), level) {}
842
844 // Ensure that all expected diagnostics were handled.
845 (void)verify();
846}
847
848/// Returns the status of the verifier and verifies that all expected
849/// diagnostics were emitted. This return success if all diagnostics were
850/// verified correctly, failure otherwise.
852 // Verify that all expected errors were seen.
853 auto checkExpectedDiags = [&](ExpectedDiag &err) {
854 if (!err.matched)
855 impl->status =
856 err.emitError(os, mgr,
857 "expected " + getDiagKindStr(err.kind) + " \"" +
858 err.substring + "\" was not produced");
859 };
860 for (auto &expectedDiagsPair : impl->expectedDiagsPerFile)
861 for (auto &err : expectedDiagsPair.second)
862 checkExpectedDiags(err);
863 for (auto &err : impl->expectedUnknownLocDiags)
864 checkExpectedDiags(err);
865 impl->expectedDiagsPerFile.clear();
866 return impl->status;
867}
868
870 ctx->getDiagEngine().registerHandler([&](Diagnostic &diag) {
871 // Process the main diagnostics.
872 process(diag);
873
874 // Process each of the notes.
875 for (auto &note : diag.getNotes())
876 process(note);
877 });
878}
879
880/// Process a single diagnostic.
881void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) {
882 return process(diag.getLocation(), diag.str(), diag.getSeverity());
883}
884
885/// Process a diagnostic at a certain location.
886void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,
887 StringRef msg,
888 DiagnosticSeverity kind) {
891
892 if (fileLoc) {
893 // Get the expected diagnostics for this file.
894 if (auto maybeDiags = impl->getExpectedDiags(fileLoc.getFilename())) {
895 diags = *maybeDiags;
896 } else {
897 diags = impl->computeExpectedDiags(
898 os, mgr, getBufferForFile(fileLoc.getFilename()));
899 }
900 } else {
901 // Get all expected diagnostics at unknown locations.
902 diags = impl->expectedUnknownLocDiags;
903 }
904
905 // Search for a matching expected diagnostic.
906 // If we find something that is close then emit a more specific error.
907 ExpectedDiag *nearMiss = nullptr;
908
909 // If this was an expected error, remember that we saw it and return.
910 for (auto &e : diags) {
911 // File line must match (unless it's an unknown location).
912 if (fileLoc && fileLoc.getLine() != e.lineNo)
913 continue;
914 if (e.match(msg)) {
915 if (e.kind == kind) {
916 e.matched = true;
917 return;
918 }
919
920 // If this only differs based on the diagnostic kind, then consider it
921 // to be a near miss.
922 nearMiss = &e;
923 }
924 }
925
926 if (impl->getVerifyLevel() == Level::OnlyExpected)
927 return;
928
929 // Otherwise, emit an error for the near miss.
930 if (nearMiss)
931 mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error,
932 "'" + getDiagKindStr(kind) +
933 "' diagnostic emitted when expecting a '" +
934 getDiagKindStr(nearMiss->kind) + "'");
935 else
936 emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
938 impl->status = failure();
939}
940
941//===----------------------------------------------------------------------===//
942// ParallelDiagnosticHandler
943//===----------------------------------------------------------------------===//
944
945namespace mlir {
946namespace detail {
947struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry {
950 : id(id), diag(std::move(diag)) {}
951 bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; }
952
953 /// The id for this diagnostic, this is used for ordering.
954 /// Note: This id corresponds to the ordered position of the current element
955 /// being processed by a given thread.
956 size_t id;
957
958 /// The diagnostic.
960 };
961
963 handlerID = ctx->getDiagEngine().registerHandler([this](Diagnostic &diag) {
964 uint64_t tid = llvm::get_threadid();
965 llvm::sys::SmartScopedLock<true> lock(mutex);
966
967 // If this thread is not tracked, then return failure to let another
968 // handler process this diagnostic.
969 if (!threadToOrderID.count(tid))
970 return failure();
971
972 // Append a new diagnostic.
973 diagnostics.emplace_back(threadToOrderID[tid], std::move(diag));
974 return success();
975 });
976 }
977
979 // Erase this handler from the context.
980 context->getDiagEngine().eraseHandler(handlerID);
981
982 // Early exit if there are no diagnostics, this is the common case.
983 if (diagnostics.empty())
984 return;
985
986 // Emit the diagnostics back to the context.
988 return context->getDiagEngine().emit(std::move(diag));
989 });
990 }
991
992 /// Utility method to emit any held diagnostics.
993 void emitDiagnostics(llvm::function_ref<void(Diagnostic &)> emitFn) const {
994 // Stable sort all of the diagnostics that were emitted. This creates a
995 // deterministic ordering for the diagnostics based upon which order id they
996 // were emitted for.
997 llvm::stable_sort(diagnostics);
998
999 // Emit each diagnostic to the context again.
1001 emitFn(diag.diag);
1002 }
1003
1004 /// Set the order id for the current thread.
1005 void setOrderIDForThread(size_t orderID) {
1006 uint64_t tid = llvm::get_threadid();
1007 llvm::sys::SmartScopedLock<true> lock(mutex);
1008 threadToOrderID[tid] = orderID;
1009 }
1010
1011 /// Remove the order id for the current thread.
1013 uint64_t tid = llvm::get_threadid();
1014 llvm::sys::SmartScopedLock<true> lock(mutex);
1015 threadToOrderID.erase(tid);
1016 }
1017
1018 /// Dump the current diagnostics that were inflight.
1019 void print(raw_ostream &os) const override {
1020 // Early exit if there are no diagnostics, this is the common case.
1021 if (diagnostics.empty())
1022 return;
1023
1024 os << "In-Flight Diagnostics:\n";
1025 emitDiagnostics([&](const Diagnostic &diag) {
1026 os.indent(4);
1027
1028 // Print each diagnostic with the format:
1029 // "<location>: <kind>: <msg>"
1030 if (!llvm::isa<UnknownLoc>(diag.getLocation()))
1031 os << diag.getLocation() << ": ";
1032 switch (diag.getSeverity()) {
1034 os << "error: ";
1035 break;
1037 os << "warning: ";
1038 break;
1040 os << "note: ";
1041 break;
1043 os << "remark: ";
1044 break;
1045 }
1046 os << diag << '\n';
1047 });
1048 }
1049
1050 /// A smart mutex to lock access to the internal state.
1052
1053 /// A mapping between the thread id and the current order id.
1055
1056 /// An unordered list of diagnostics that were emitted.
1057 mutable std::vector<ThreadDiagnostic> diagnostics;
1058
1059 /// The unique id for the parallel handler.
1061
1062 /// The context to emit the diagnostics to.
1064};
1065} // namespace detail
1066} // namespace mlir
1067
1071
1072/// Set the order id for the current thread.
1074 impl->setOrderIDForThread(orderID);
1075}
1076
1077/// Remove the order id for the current thread. This removes the thread from
1078/// diagnostics tracking.
1080 impl->eraseOrderIDForThread();
1081}
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:1143
Operation * getOperation() const
Definition Operation.h:1149
OpPrintingFlags & flags()
Definition Operation.h:1147
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: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: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: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.