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. 34 DiagnosticArgument::DiagnosticArgument(Attribute attr) 35 : kind(DiagnosticArgumentKind::Attribute), 36 opaqueVal(reinterpret_cast<intptr_t>(attr.getAsOpaquePointer())) {} 37 38 /// Construct from a Type. 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. 44 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. 51 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. 57 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'. 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. 99 Diagnostic &Diagnostic::operator<<(char val) { return *this << Twine(val); } 100 Diagnostic &Diagnostic::operator<<(const Twine &val) { 101 arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings))); 102 return *this; 103 } 104 Diagnostic &Diagnostic::operator<<(Twine &&val) { 105 arguments.push_back(DiagnosticArgument(twineToStrRef(val, strings))); 106 return *this; 107 } 108 109 /// Stream in an Identifier. 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. 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. 126 Diagnostic &Diagnostic::operator<<(Operation &val) { 127 std::string str; 128 llvm::raw_string_ostream os(str); 129 val.print(os, OpPrintingFlags().useLocalScope().elideLargeElementsAttrs()); 130 return *this << os.str(); 131 } 132 133 /// Outputs this diagnostic to a stream. 134 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. 140 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. 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'. 166 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. 174 InFlightDiagnostic::operator LogicalResult() const { 175 return failure(isActive()); 176 } 177 178 /// Reports the diagnostic to the engine. 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. 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. 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 248 DiagnosticEngine::DiagnosticEngine() : impl(new DiagnosticEngineImpl()) {} 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. 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. 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. 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 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. 301 InFlightDiagnostic mlir::emitError(Location loc) { return emitError(loc, {}); } 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. 307 InFlightDiagnostic mlir::emitWarning(Location loc) { 308 return emitWarning(loc, {}); 309 } 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. 315 InFlightDiagnostic mlir::emitRemark(Location loc) { 316 return emitRemark(loc, {}); 317 } 318 InFlightDiagnostic mlir::emitRemark(Location loc, const Twine &message) { 319 return emitDiag(loc, DiagnosticSeverity::Remark, message); 320 } 321 322 //===----------------------------------------------------------------------===// 323 // ScopedDiagnosticHandler 324 //===----------------------------------------------------------------------===// 325 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. 339 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. 368 static Optional<FileLineColLoc> getFileLineColLoc(Location loc) { 369 Optional<FileLineColLoc> firstFileLoc; 370 loc->walk([&](Location loc) { 371 if (FileLineColLoc fileLoc = loc.dyn_cast<FileLineColLoc>()) { 372 firstFileLoc = fileLoc; 373 return WalkResult::interrupt(); 374 } 375 return WalkResult::advance(); 376 }); 377 return firstFileLoc; 378 } 379 380 /// Return a processable CallSiteLoc from the given location. 381 static Optional<CallSiteLoc> getCallSiteLoc(Location loc) { 382 if (auto nameLoc = loc.dyn_cast<NameLoc>()) 383 return getCallSiteLoc(loc.cast<NameLoc>().getChildLoc()); 384 if (auto callLoc = loc.dyn_cast<CallSiteLoc>()) 385 return callLoc; 386 if (auto fusedLoc = loc.dyn_cast<FusedLoc>()) { 387 for (auto subLoc : loc.cast<FusedLoc>().getLocations()) { 388 if (auto callLoc = getCallSiteLoc(subLoc)) { 389 return callLoc; 390 } 391 } 392 return llvm::None; 393 } 394 return llvm::None; 395 } 396 397 /// Given a diagnostic kind, returns the LLVM DiagKind. 398 static llvm::SourceMgr::DiagKind getDiagKind(DiagnosticSeverity kind) { 399 switch (kind) { 400 case DiagnosticSeverity::Note: 401 return llvm::SourceMgr::DK_Note; 402 case DiagnosticSeverity::Warning: 403 return llvm::SourceMgr::DK_Warning; 404 case DiagnosticSeverity::Error: 405 return llvm::SourceMgr::DK_Error; 406 case DiagnosticSeverity::Remark: 407 return llvm::SourceMgr::DK_Remark; 408 } 409 llvm_unreachable("Unknown DiagnosticSeverity"); 410 } 411 412 SourceMgrDiagnosticHandler::SourceMgrDiagnosticHandler(llvm::SourceMgr &mgr, 413 MLIRContext *ctx, 414 raw_ostream &os) 415 : ScopedDiagnosticHandler(ctx), mgr(mgr), os(os), 416 impl(new SourceMgrDiagnosticHandlerImpl()) { 417 setHandler([this](Diagnostic &diag) { emitDiagnostic(diag); }); 418 } 419 420 SourceMgrDiagnosticHandler::SourceMgrDiagnosticHandler(llvm::SourceMgr &mgr, 421 MLIRContext *ctx) 422 : SourceMgrDiagnosticHandler(mgr, ctx, llvm::errs()) {} 423 424 SourceMgrDiagnosticHandler::~SourceMgrDiagnosticHandler() {} 425 426 void SourceMgrDiagnosticHandler::emitDiagnostic(Location loc, Twine message, 427 DiagnosticSeverity kind, 428 bool displaySourceLine) { 429 // Extract a file location from this loc. 430 auto fileLoc = getFileLineColLoc(loc); 431 432 // If one doesn't exist, then print the raw message without a source location. 433 if (!fileLoc) { 434 std::string str; 435 llvm::raw_string_ostream strOS(str); 436 if (!loc.isa<UnknownLoc>()) 437 strOS << loc << ": "; 438 strOS << message; 439 return mgr.PrintMessage(os, llvm::SMLoc(), getDiagKind(kind), strOS.str()); 440 } 441 442 // Otherwise if we are displaying the source line, try to convert the file 443 // location to an SMLoc. 444 if (displaySourceLine) { 445 auto smloc = convertLocToSMLoc(*fileLoc); 446 if (smloc.isValid()) 447 return mgr.PrintMessage(os, smloc, getDiagKind(kind), message); 448 } 449 450 // If the conversion was unsuccessful, create a diagnostic with the file 451 // information. We manually combine the line and column to avoid asserts in 452 // the constructor of SMDiagnostic that takes a location. 453 std::string locStr; 454 llvm::raw_string_ostream locOS(locStr); 455 locOS << fileLoc->getFilename() << ":" << fileLoc->getLine() << ":" 456 << fileLoc->getColumn(); 457 llvm::SMDiagnostic diag(locOS.str(), getDiagKind(kind), message.str()); 458 diag.print(nullptr, os); 459 } 460 461 /// Emit the given diagnostic with the held source manager. 462 void SourceMgrDiagnosticHandler::emitDiagnostic(Diagnostic &diag) { 463 // Emit the diagnostic. 464 Location loc = diag.getLocation(); 465 emitDiagnostic(loc, diag.str(), diag.getSeverity()); 466 467 // If the diagnostic location was a call site location, then print the call 468 // stack as well. 469 if (auto callLoc = getCallSiteLoc(loc)) { 470 // Print the call stack while valid, or until the limit is reached. 471 loc = callLoc->getCaller(); 472 for (unsigned curDepth = 0; curDepth < callStackLimit; ++curDepth) { 473 emitDiagnostic(loc, "called from", DiagnosticSeverity::Note); 474 if ((callLoc = getCallSiteLoc(loc))) 475 loc = callLoc->getCaller(); 476 else 477 break; 478 } 479 } 480 481 // Emit each of the notes. Only display the source code if the location is 482 // different from the previous location. 483 for (auto ¬e : diag.getNotes()) { 484 emitDiagnostic(note.getLocation(), note.str(), note.getSeverity(), 485 /*displaySourceLine=*/loc != note.getLocation()); 486 loc = note.getLocation(); 487 } 488 } 489 490 /// Get a memory buffer for the given file, or nullptr if one is not found. 491 const llvm::MemoryBuffer * 492 SourceMgrDiagnosticHandler::getBufferForFile(StringRef filename) { 493 if (unsigned id = impl->getSourceMgrBufferIDForFile(mgr, filename)) 494 return mgr.getMemoryBuffer(id); 495 return nullptr; 496 } 497 498 /// Get a memory buffer for the given file, or the main file of the source 499 /// manager if one doesn't exist. This always returns non-null. 500 llvm::SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) { 501 // The column and line may be zero to represent unknown column and/or unknown 502 /// line/column information. 503 if (loc.getLine() == 0 || loc.getColumn() == 0) 504 return llvm::SMLoc(); 505 506 unsigned bufferId = impl->getSourceMgrBufferIDForFile(mgr, loc.getFilename()); 507 if (!bufferId) 508 return llvm::SMLoc(); 509 return mgr.FindLocForLineAndColumn(bufferId, loc.getLine(), loc.getColumn()); 510 } 511 512 //===----------------------------------------------------------------------===// 513 // SourceMgrDiagnosticVerifierHandler 514 //===----------------------------------------------------------------------===// 515 516 namespace mlir { 517 namespace detail { 518 // Record the expected diagnostic's position, substring and whether it was 519 // seen. 520 struct ExpectedDiag { 521 DiagnosticSeverity kind; 522 unsigned lineNo; 523 StringRef substring; 524 llvm::SMLoc fileLoc; 525 bool matched; 526 }; 527 528 struct SourceMgrDiagnosticVerifierHandlerImpl { 529 SourceMgrDiagnosticVerifierHandlerImpl() : status(success()) {} 530 531 /// Returns the expected diagnostics for the given source file. 532 Optional<MutableArrayRef<ExpectedDiag>> getExpectedDiags(StringRef bufName); 533 534 /// Computes the expected diagnostics for the given source buffer. 535 MutableArrayRef<ExpectedDiag> 536 computeExpectedDiags(const llvm::MemoryBuffer *buf); 537 538 /// The current status of the verifier. 539 LogicalResult status; 540 541 /// A list of expected diagnostics for each buffer of the source manager. 542 llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile; 543 544 /// Regex to match the expected diagnostics format. 545 llvm::Regex expected = llvm::Regex("expected-(error|note|remark|warning) " 546 "*(@([+-][0-9]+|above|below))? *{{(.*)}}"); 547 }; 548 } // end namespace detail 549 } // end namespace mlir 550 551 /// Given a diagnostic kind, return a human readable string for it. 552 static StringRef getDiagKindStr(DiagnosticSeverity kind) { 553 switch (kind) { 554 case DiagnosticSeverity::Note: 555 return "note"; 556 case DiagnosticSeverity::Warning: 557 return "warning"; 558 case DiagnosticSeverity::Error: 559 return "error"; 560 case DiagnosticSeverity::Remark: 561 return "remark"; 562 } 563 llvm_unreachable("Unknown DiagnosticSeverity"); 564 } 565 566 /// Returns the expected diagnostics for the given source file. 567 Optional<MutableArrayRef<ExpectedDiag>> 568 SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) { 569 auto expectedDiags = expectedDiagsPerFile.find(bufName); 570 if (expectedDiags != expectedDiagsPerFile.end()) 571 return MutableArrayRef<ExpectedDiag>(expectedDiags->second); 572 return llvm::None; 573 } 574 575 /// Computes the expected diagnostics for the given source buffer. 576 MutableArrayRef<ExpectedDiag> 577 SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags( 578 const llvm::MemoryBuffer *buf) { 579 // If the buffer is invalid, return an empty list. 580 if (!buf) 581 return llvm::None; 582 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()]; 583 584 // The number of the last line that did not correlate to a designator. 585 unsigned lastNonDesignatorLine = 0; 586 587 // The indices of designators that apply to the next non designator line. 588 SmallVector<unsigned, 1> designatorsForNextLine; 589 590 // Scan the file for expected-* designators. 591 SmallVector<StringRef, 100> lines; 592 buf->getBuffer().split(lines, '\n'); 593 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) { 594 SmallVector<StringRef, 4> matches; 595 if (!expected.match(lines[lineNo], &matches)) { 596 // Check for designators that apply to this line. 597 if (!designatorsForNextLine.empty()) { 598 for (unsigned diagIndex : designatorsForNextLine) 599 expectedDiags[diagIndex].lineNo = lineNo + 1; 600 designatorsForNextLine.clear(); 601 } 602 lastNonDesignatorLine = lineNo; 603 continue; 604 } 605 606 // Point to the start of expected-*. 607 auto expectedStart = llvm::SMLoc::getFromPointer(matches[0].data()); 608 609 DiagnosticSeverity kind; 610 if (matches[1] == "error") 611 kind = DiagnosticSeverity::Error; 612 else if (matches[1] == "warning") 613 kind = DiagnosticSeverity::Warning; 614 else if (matches[1] == "remark") 615 kind = DiagnosticSeverity::Remark; 616 else { 617 assert(matches[1] == "note"); 618 kind = DiagnosticSeverity::Note; 619 } 620 621 ExpectedDiag record{kind, lineNo + 1, matches[4], expectedStart, false}; 622 auto offsetMatch = matches[2]; 623 if (!offsetMatch.empty()) { 624 offsetMatch = offsetMatch.drop_front(1); 625 626 // Get the integer value without the @ and +/- prefix. 627 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') { 628 int offset; 629 offsetMatch.drop_front().getAsInteger(0, offset); 630 631 if (offsetMatch.front() == '+') 632 record.lineNo += offset; 633 else 634 record.lineNo -= offset; 635 } else if (offsetMatch.consume_front("above")) { 636 // If the designator applies 'above' we add it to the last non 637 // designator line. 638 record.lineNo = lastNonDesignatorLine + 1; 639 } else { 640 // Otherwise, this is a 'below' designator and applies to the next 641 // non-designator line. 642 assert(offsetMatch.consume_front("below")); 643 designatorsForNextLine.push_back(expectedDiags.size()); 644 645 // Set the line number to the last in the case that this designator ends 646 // up dangling. 647 record.lineNo = e; 648 } 649 } 650 expectedDiags.push_back(record); 651 } 652 return expectedDiags; 653 } 654 655 SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler( 656 llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out) 657 : SourceMgrDiagnosticHandler(srcMgr, ctx, out), 658 impl(new SourceMgrDiagnosticVerifierHandlerImpl()) { 659 // Compute the expected diagnostics for each of the current files in the 660 // source manager. 661 for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i) 662 (void)impl->computeExpectedDiags(mgr.getMemoryBuffer(i + 1)); 663 664 // Register a handler to verify the diagnostics. 665 setHandler([&](Diagnostic &diag) { 666 // Process the main diagnostics. 667 process(diag); 668 669 // Process each of the notes. 670 for (auto ¬e : diag.getNotes()) 671 process(note); 672 }); 673 } 674 675 SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler( 676 llvm::SourceMgr &srcMgr, MLIRContext *ctx) 677 : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs()) {} 678 679 SourceMgrDiagnosticVerifierHandler::~SourceMgrDiagnosticVerifierHandler() { 680 // Ensure that all expected diagnostics were handled. 681 (void)verify(); 682 } 683 684 /// Returns the status of the verifier and verifies that all expected 685 /// diagnostics were emitted. This return success if all diagnostics were 686 /// verified correctly, failure otherwise. 687 LogicalResult SourceMgrDiagnosticVerifierHandler::verify() { 688 // Verify that all expected errors were seen. 689 for (auto &expectedDiagsPair : impl->expectedDiagsPerFile) { 690 for (auto &err : expectedDiagsPair.second) { 691 if (err.matched) 692 continue; 693 llvm::SMRange range(err.fileLoc, 694 llvm::SMLoc::getFromPointer(err.fileLoc.getPointer() + 695 err.substring.size())); 696 mgr.PrintMessage(os, err.fileLoc, llvm::SourceMgr::DK_Error, 697 "expected " + getDiagKindStr(err.kind) + " \"" + 698 err.substring + "\" was not produced", 699 range); 700 impl->status = failure(); 701 } 702 } 703 impl->expectedDiagsPerFile.clear(); 704 return impl->status; 705 } 706 707 /// Process a single diagnostic. 708 void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) { 709 auto kind = diag.getSeverity(); 710 711 // Process a FileLineColLoc. 712 if (auto fileLoc = getFileLineColLoc(diag.getLocation())) 713 return process(*fileLoc, diag.str(), kind); 714 715 emitDiagnostic(diag.getLocation(), 716 "unexpected " + getDiagKindStr(kind) + ": " + diag.str(), 717 DiagnosticSeverity::Error); 718 impl->status = failure(); 719 } 720 721 /// Process a FileLineColLoc diagnostic. 722 void SourceMgrDiagnosticVerifierHandler::process(FileLineColLoc loc, 723 StringRef msg, 724 DiagnosticSeverity kind) { 725 // Get the expected diagnostics for this file. 726 auto diags = impl->getExpectedDiags(loc.getFilename()); 727 if (!diags) 728 diags = impl->computeExpectedDiags(getBufferForFile(loc.getFilename())); 729 730 // Search for a matching expected diagnostic. 731 // If we find something that is close then emit a more specific error. 732 ExpectedDiag *nearMiss = nullptr; 733 734 // If this was an expected error, remember that we saw it and return. 735 unsigned line = loc.getLine(); 736 for (auto &e : *diags) { 737 if (line == e.lineNo && msg.contains(e.substring)) { 738 if (e.kind == kind) { 739 e.matched = true; 740 return; 741 } 742 743 // If this only differs based on the diagnostic kind, then consider it 744 // to be a near miss. 745 nearMiss = &e; 746 } 747 } 748 749 // Otherwise, emit an error for the near miss. 750 if (nearMiss) 751 mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error, 752 "'" + getDiagKindStr(kind) + 753 "' diagnostic emitted when expecting a '" + 754 getDiagKindStr(nearMiss->kind) + "'"); 755 else 756 emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg, 757 DiagnosticSeverity::Error); 758 impl->status = failure(); 759 } 760 761 //===----------------------------------------------------------------------===// 762 // ParallelDiagnosticHandler 763 //===----------------------------------------------------------------------===// 764 765 namespace mlir { 766 namespace detail { 767 struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry { 768 struct ThreadDiagnostic { 769 ThreadDiagnostic(size_t id, Diagnostic diag) 770 : id(id), diag(std::move(diag)) {} 771 bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; } 772 773 /// The id for this diagnostic, this is used for ordering. 774 /// Note: This id corresponds to the ordered position of the current element 775 /// being processed by a given thread. 776 size_t id; 777 778 /// The diagnostic. 779 Diagnostic diag; 780 }; 781 782 ParallelDiagnosticHandlerImpl(MLIRContext *ctx) : handlerID(0), context(ctx) { 783 handlerID = ctx->getDiagEngine().registerHandler([this](Diagnostic &diag) { 784 uint64_t tid = llvm::get_threadid(); 785 llvm::sys::SmartScopedLock<true> lock(mutex); 786 787 // If this thread is not tracked, then return failure to let another 788 // handler process this diagnostic. 789 if (!threadToOrderID.count(tid)) 790 return failure(); 791 792 // Append a new diagnostic. 793 diagnostics.emplace_back(threadToOrderID[tid], std::move(diag)); 794 return success(); 795 }); 796 } 797 798 ~ParallelDiagnosticHandlerImpl() override { 799 // Erase this handler from the context. 800 context->getDiagEngine().eraseHandler(handlerID); 801 802 // Early exit if there are no diagnostics, this is the common case. 803 if (diagnostics.empty()) 804 return; 805 806 // Emit the diagnostics back to the context. 807 emitDiagnostics([&](Diagnostic diag) { 808 return context->getDiagEngine().emit(std::move(diag)); 809 }); 810 } 811 812 /// Utility method to emit any held diagnostics. 813 void emitDiagnostics(std::function<void(Diagnostic)> emitFn) const { 814 // Stable sort all of the diagnostics that were emitted. This creates a 815 // deterministic ordering for the diagnostics based upon which order id they 816 // were emitted for. 817 std::stable_sort(diagnostics.begin(), diagnostics.end()); 818 819 // Emit each diagnostic to the context again. 820 for (ThreadDiagnostic &diag : diagnostics) 821 emitFn(std::move(diag.diag)); 822 } 823 824 /// Set the order id for the current thread. 825 void setOrderIDForThread(size_t orderID) { 826 uint64_t tid = llvm::get_threadid(); 827 llvm::sys::SmartScopedLock<true> lock(mutex); 828 threadToOrderID[tid] = orderID; 829 } 830 831 /// Remove the order id for the current thread. 832 void eraseOrderIDForThread() { 833 uint64_t tid = llvm::get_threadid(); 834 llvm::sys::SmartScopedLock<true> lock(mutex); 835 threadToOrderID.erase(tid); 836 } 837 838 /// Dump the current diagnostics that were inflight. 839 void print(raw_ostream &os) const override { 840 // Early exit if there are no diagnostics, this is the common case. 841 if (diagnostics.empty()) 842 return; 843 844 os << "In-Flight Diagnostics:\n"; 845 emitDiagnostics([&](Diagnostic diag) { 846 os.indent(4); 847 848 // Print each diagnostic with the format: 849 // "<location>: <kind>: <msg>" 850 if (!diag.getLocation().isa<UnknownLoc>()) 851 os << diag.getLocation() << ": "; 852 switch (diag.getSeverity()) { 853 case DiagnosticSeverity::Error: 854 os << "error: "; 855 break; 856 case DiagnosticSeverity::Warning: 857 os << "warning: "; 858 break; 859 case DiagnosticSeverity::Note: 860 os << "note: "; 861 break; 862 case DiagnosticSeverity::Remark: 863 os << "remark: "; 864 break; 865 } 866 os << diag << '\n'; 867 }); 868 } 869 870 /// A smart mutex to lock access to the internal state. 871 llvm::sys::SmartMutex<true> mutex; 872 873 /// A mapping between the thread id and the current order id. 874 DenseMap<uint64_t, size_t> threadToOrderID; 875 876 /// An unordered list of diagnostics that were emitted. 877 mutable std::vector<ThreadDiagnostic> diagnostics; 878 879 /// The unique id for the parallel handler. 880 DiagnosticEngine::HandlerID handlerID; 881 882 /// The context to emit the diagnostics to. 883 MLIRContext *context; 884 }; 885 } // end namespace detail 886 } // end namespace mlir 887 888 ParallelDiagnosticHandler::ParallelDiagnosticHandler(MLIRContext *ctx) 889 : impl(new ParallelDiagnosticHandlerImpl(ctx)) {} 890 ParallelDiagnosticHandler::~ParallelDiagnosticHandler() {} 891 892 /// Set the order id for the current thread. 893 void ParallelDiagnosticHandler::setOrderIDForThread(size_t orderID) { 894 impl->setOrderIDForThread(orderID); 895 } 896 897 /// Remove the order id for the current thread. This removes the thread from 898 /// diagnostics tracking. 899 void ParallelDiagnosticHandler::eraseOrderIDForThread() { 900 impl->eraseOrderIDForThread(); 901 } 902