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 os << val; 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 switch (loc->getKind()) { 370 case StandardAttributes::NameLocation: 371 return getFileLineColLoc(loc.cast<NameLoc>().getChildLoc()); 372 case StandardAttributes::FileLineColLocation: 373 return loc.cast<FileLineColLoc>(); 374 case StandardAttributes::CallSiteLocation: 375 // Process the callee of a callsite location. 376 return getFileLineColLoc(loc.cast<CallSiteLoc>().getCallee()); 377 case StandardAttributes::FusedLocation: 378 for (auto subLoc : loc.cast<FusedLoc>().getLocations()) { 379 if (auto callLoc = getFileLineColLoc(subLoc)) { 380 return callLoc; 381 } 382 } 383 return llvm::None; 384 default: 385 return llvm::None; 386 } 387 } 388 389 /// Return a processable CallSiteLoc from the given location. 390 static Optional<CallSiteLoc> getCallSiteLoc(Location loc) { 391 switch (loc->getKind()) { 392 case StandardAttributes::NameLocation: 393 return getCallSiteLoc(loc.cast<NameLoc>().getChildLoc()); 394 case StandardAttributes::CallSiteLocation: 395 return loc.cast<CallSiteLoc>(); 396 case StandardAttributes::FusedLocation: 397 for (auto subLoc : loc.cast<FusedLoc>().getLocations()) { 398 if (auto callLoc = getCallSiteLoc(subLoc)) { 399 return callLoc; 400 } 401 } 402 return llvm::None; 403 default: 404 return llvm::None; 405 } 406 } 407 408 /// Given a diagnostic kind, returns the LLVM DiagKind. 409 static llvm::SourceMgr::DiagKind getDiagKind(DiagnosticSeverity kind) { 410 switch (kind) { 411 case DiagnosticSeverity::Note: 412 return llvm::SourceMgr::DK_Note; 413 case DiagnosticSeverity::Warning: 414 return llvm::SourceMgr::DK_Warning; 415 case DiagnosticSeverity::Error: 416 return llvm::SourceMgr::DK_Error; 417 case DiagnosticSeverity::Remark: 418 return llvm::SourceMgr::DK_Remark; 419 } 420 llvm_unreachable("Unknown DiagnosticSeverity"); 421 } 422 423 SourceMgrDiagnosticHandler::SourceMgrDiagnosticHandler(llvm::SourceMgr &mgr, 424 MLIRContext *ctx, 425 raw_ostream &os) 426 : ScopedDiagnosticHandler(ctx), mgr(mgr), os(os), 427 impl(new SourceMgrDiagnosticHandlerImpl()) { 428 setHandler([this](Diagnostic &diag) { emitDiagnostic(diag); }); 429 } 430 431 SourceMgrDiagnosticHandler::SourceMgrDiagnosticHandler(llvm::SourceMgr &mgr, 432 MLIRContext *ctx) 433 : SourceMgrDiagnosticHandler(mgr, ctx, llvm::errs()) {} 434 435 SourceMgrDiagnosticHandler::~SourceMgrDiagnosticHandler() {} 436 437 void SourceMgrDiagnosticHandler::emitDiagnostic(Location loc, Twine message, 438 DiagnosticSeverity kind, 439 bool displaySourceLine) { 440 // Extract a file location from this loc. 441 auto fileLoc = getFileLineColLoc(loc); 442 443 // If one doesn't exist, then print the raw message without a source location. 444 if (!fileLoc) { 445 std::string str; 446 llvm::raw_string_ostream strOS(str); 447 if (!loc.isa<UnknownLoc>()) 448 strOS << loc << ": "; 449 strOS << message; 450 return mgr.PrintMessage(os, llvm::SMLoc(), getDiagKind(kind), strOS.str()); 451 } 452 453 // Otherwise if we are displaying the source line, try to convert the file 454 // location to an SMLoc. 455 if (displaySourceLine) { 456 auto smloc = convertLocToSMLoc(*fileLoc); 457 if (smloc.isValid()) 458 return mgr.PrintMessage(os, smloc, getDiagKind(kind), message); 459 } 460 461 // If the conversion was unsuccessful, create a diagnostic with the file 462 // information. We manually combine the line and column to avoid asserts in 463 // the constructor of SMDiagnostic that takes a location. 464 std::string locStr; 465 llvm::raw_string_ostream locOS(locStr); 466 locOS << fileLoc->getFilename() << ":" << fileLoc->getLine() << ":" 467 << fileLoc->getColumn(); 468 llvm::SMDiagnostic diag(locOS.str(), getDiagKind(kind), message.str()); 469 diag.print(nullptr, os); 470 } 471 472 /// Emit the given diagnostic with the held source manager. 473 void SourceMgrDiagnosticHandler::emitDiagnostic(Diagnostic &diag) { 474 // Emit the diagnostic. 475 Location loc = diag.getLocation(); 476 emitDiagnostic(loc, diag.str(), diag.getSeverity()); 477 478 // If the diagnostic location was a call site location, then print the call 479 // stack as well. 480 if (auto callLoc = getCallSiteLoc(loc)) { 481 // Print the call stack while valid, or until the limit is reached. 482 loc = callLoc->getCaller(); 483 for (unsigned curDepth = 0; curDepth < callStackLimit; ++curDepth) { 484 emitDiagnostic(loc, "called from", DiagnosticSeverity::Note); 485 if ((callLoc = getCallSiteLoc(loc))) 486 loc = callLoc->getCaller(); 487 else 488 break; 489 } 490 } 491 492 // Emit each of the notes. Only display the source code if the location is 493 // different from the previous location. 494 for (auto ¬e : diag.getNotes()) { 495 emitDiagnostic(note.getLocation(), note.str(), note.getSeverity(), 496 /*displaySourceLine=*/loc != note.getLocation()); 497 loc = note.getLocation(); 498 } 499 } 500 501 /// Get a memory buffer for the given file, or nullptr if one is not found. 502 const llvm::MemoryBuffer * 503 SourceMgrDiagnosticHandler::getBufferForFile(StringRef filename) { 504 if (unsigned id = impl->getSourceMgrBufferIDForFile(mgr, filename)) 505 return mgr.getMemoryBuffer(id); 506 return nullptr; 507 } 508 509 /// Get a memory buffer for the given file, or the main file of the source 510 /// manager if one doesn't exist. This always returns non-null. 511 llvm::SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) { 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 { 535 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. 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>> 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> 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 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 681 SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler( 682 llvm::SourceMgr &srcMgr, MLIRContext *ctx) 683 : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs()) {} 684 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. 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. 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. 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 { 775 ThreadDiagnostic(size_t id, Diagnostic diag) 776 : id(id), diag(std::move(diag)) {} 777 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 788 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 804 ~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. 819 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. 831 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. 838 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. 845 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 894 ParallelDiagnosticHandler::ParallelDiagnosticHandler(MLIRContext *ctx) 895 : impl(new ParallelDiagnosticHandlerImpl(ctx)) {} 896 ParallelDiagnosticHandler::~ParallelDiagnosticHandler() {} 897 898 /// Set the order id for the current thread. 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. 905 void ParallelDiagnosticHandler::eraseOrderIDForThread() { 906 impl->eraseOrderIDForThread(); 907 } 908