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