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 // The column and line may be zero to represent unknown column and/or unknown 513 /// line/column information. 514 if (loc.getLine() == 0 || loc.getColumn() == 0) 515 return llvm::SMLoc(); 516 517 unsigned bufferId = impl->getSourceMgrBufferIDForFile(mgr, loc.getFilename()); 518 if (!bufferId) 519 return llvm::SMLoc(); 520 return mgr.FindLocForLineAndColumn(bufferId, loc.getLine(), loc.getColumn()); 521 } 522 523 //===----------------------------------------------------------------------===// 524 // SourceMgrDiagnosticVerifierHandler 525 //===----------------------------------------------------------------------===// 526 527 namespace mlir { 528 namespace detail { 529 // Record the expected diagnostic's position, substring and whether it was 530 // seen. 531 struct ExpectedDiag { 532 DiagnosticSeverity kind; 533 unsigned lineNo; 534 StringRef substring; 535 llvm::SMLoc fileLoc; 536 bool matched; 537 }; 538 539 struct SourceMgrDiagnosticVerifierHandlerImpl { 540 SourceMgrDiagnosticVerifierHandlerImpl() : status(success()) {} 541 542 /// Returns the expected diagnostics for the given source file. 543 Optional<MutableArrayRef<ExpectedDiag>> getExpectedDiags(StringRef bufName); 544 545 /// Computes the expected diagnostics for the given source buffer. 546 MutableArrayRef<ExpectedDiag> 547 computeExpectedDiags(const llvm::MemoryBuffer *buf); 548 549 /// The current status of the verifier. 550 LogicalResult status; 551 552 /// A list of expected diagnostics for each buffer of the source manager. 553 llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile; 554 555 /// Regex to match the expected diagnostics format. 556 llvm::Regex expected = llvm::Regex("expected-(error|note|remark|warning) " 557 "*(@([+-][0-9]+|above|below))? *{{(.*)}}"); 558 }; 559 } // end namespace detail 560 } // end namespace mlir 561 562 /// Given a diagnostic kind, return a human readable string for it. 563 static StringRef getDiagKindStr(DiagnosticSeverity kind) { 564 switch (kind) { 565 case DiagnosticSeverity::Note: 566 return "note"; 567 case DiagnosticSeverity::Warning: 568 return "warning"; 569 case DiagnosticSeverity::Error: 570 return "error"; 571 case DiagnosticSeverity::Remark: 572 return "remark"; 573 } 574 llvm_unreachable("Unknown DiagnosticSeverity"); 575 } 576 577 /// Returns the expected diagnostics for the given source file. 578 Optional<MutableArrayRef<ExpectedDiag>> 579 SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) { 580 auto expectedDiags = expectedDiagsPerFile.find(bufName); 581 if (expectedDiags != expectedDiagsPerFile.end()) 582 return MutableArrayRef<ExpectedDiag>(expectedDiags->second); 583 return llvm::None; 584 } 585 586 /// Computes the expected diagnostics for the given source buffer. 587 MutableArrayRef<ExpectedDiag> 588 SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags( 589 const llvm::MemoryBuffer *buf) { 590 // If the buffer is invalid, return an empty list. 591 if (!buf) 592 return llvm::None; 593 auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()]; 594 595 // The number of the last line that did not correlate to a designator. 596 unsigned lastNonDesignatorLine = 0; 597 598 // The indices of designators that apply to the next non designator line. 599 SmallVector<unsigned, 1> designatorsForNextLine; 600 601 // Scan the file for expected-* designators. 602 SmallVector<StringRef, 100> lines; 603 buf->getBuffer().split(lines, '\n'); 604 for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) { 605 SmallVector<StringRef, 4> matches; 606 if (!expected.match(lines[lineNo], &matches)) { 607 // Check for designators that apply to this line. 608 if (!designatorsForNextLine.empty()) { 609 for (unsigned diagIndex : designatorsForNextLine) 610 expectedDiags[diagIndex].lineNo = lineNo + 1; 611 designatorsForNextLine.clear(); 612 } 613 lastNonDesignatorLine = lineNo; 614 continue; 615 } 616 617 // Point to the start of expected-*. 618 auto expectedStart = llvm::SMLoc::getFromPointer(matches[0].data()); 619 620 DiagnosticSeverity kind; 621 if (matches[1] == "error") 622 kind = DiagnosticSeverity::Error; 623 else if (matches[1] == "warning") 624 kind = DiagnosticSeverity::Warning; 625 else if (matches[1] == "remark") 626 kind = DiagnosticSeverity::Remark; 627 else { 628 assert(matches[1] == "note"); 629 kind = DiagnosticSeverity::Note; 630 } 631 632 ExpectedDiag record{kind, lineNo + 1, matches[4], expectedStart, false}; 633 auto offsetMatch = matches[2]; 634 if (!offsetMatch.empty()) { 635 offsetMatch = offsetMatch.drop_front(1); 636 637 // Get the integer value without the @ and +/- prefix. 638 if (offsetMatch[0] == '+' || offsetMatch[0] == '-') { 639 int offset; 640 offsetMatch.drop_front().getAsInteger(0, offset); 641 642 if (offsetMatch.front() == '+') 643 record.lineNo += offset; 644 else 645 record.lineNo -= offset; 646 } else if (offsetMatch.consume_front("above")) { 647 // If the designator applies 'above' we add it to the last non 648 // designator line. 649 record.lineNo = lastNonDesignatorLine + 1; 650 } else { 651 // Otherwise, this is a 'below' designator and applies to the next 652 // non-designator line. 653 assert(offsetMatch.consume_front("below")); 654 designatorsForNextLine.push_back(expectedDiags.size()); 655 656 // Set the line number to the last in the case that this designator ends 657 // up dangling. 658 record.lineNo = e; 659 } 660 } 661 expectedDiags.push_back(record); 662 } 663 return expectedDiags; 664 } 665 666 SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler( 667 llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out) 668 : SourceMgrDiagnosticHandler(srcMgr, ctx, out), 669 impl(new SourceMgrDiagnosticVerifierHandlerImpl()) { 670 // Compute the expected diagnostics for each of the current files in the 671 // source manager. 672 for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i) 673 (void)impl->computeExpectedDiags(mgr.getMemoryBuffer(i + 1)); 674 675 // Register a handler to verify the diagnostics. 676 setHandler([&](Diagnostic &diag) { 677 // Process the main diagnostics. 678 process(diag); 679 680 // Process each of the notes. 681 for (auto ¬e : diag.getNotes()) 682 process(note); 683 }); 684 } 685 686 SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler( 687 llvm::SourceMgr &srcMgr, MLIRContext *ctx) 688 : SourceMgrDiagnosticVerifierHandler(srcMgr, ctx, llvm::errs()) {} 689 690 SourceMgrDiagnosticVerifierHandler::~SourceMgrDiagnosticVerifierHandler() { 691 // Ensure that all expected diagnostics were handled. 692 (void)verify(); 693 } 694 695 /// Returns the status of the verifier and verifies that all expected 696 /// diagnostics were emitted. This return success if all diagnostics were 697 /// verified correctly, failure otherwise. 698 LogicalResult SourceMgrDiagnosticVerifierHandler::verify() { 699 // Verify that all expected errors were seen. 700 for (auto &expectedDiagsPair : impl->expectedDiagsPerFile) { 701 for (auto &err : expectedDiagsPair.second) { 702 if (err.matched) 703 continue; 704 llvm::SMRange range(err.fileLoc, 705 llvm::SMLoc::getFromPointer(err.fileLoc.getPointer() + 706 err.substring.size())); 707 mgr.PrintMessage(os, err.fileLoc, llvm::SourceMgr::DK_Error, 708 "expected " + getDiagKindStr(err.kind) + " \"" + 709 err.substring + "\" was not produced", 710 range); 711 impl->status = failure(); 712 } 713 } 714 impl->expectedDiagsPerFile.clear(); 715 return impl->status; 716 } 717 718 /// Process a single diagnostic. 719 void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) { 720 auto kind = diag.getSeverity(); 721 722 // Process a FileLineColLoc. 723 if (auto fileLoc = getFileLineColLoc(diag.getLocation())) 724 return process(*fileLoc, diag.str(), kind); 725 726 emitDiagnostic(diag.getLocation(), 727 "unexpected " + getDiagKindStr(kind) + ": " + diag.str(), 728 DiagnosticSeverity::Error); 729 impl->status = failure(); 730 } 731 732 /// Process a FileLineColLoc diagnostic. 733 void SourceMgrDiagnosticVerifierHandler::process(FileLineColLoc loc, 734 StringRef msg, 735 DiagnosticSeverity kind) { 736 // Get the expected diagnostics for this file. 737 auto diags = impl->getExpectedDiags(loc.getFilename()); 738 if (!diags) 739 diags = impl->computeExpectedDiags(getBufferForFile(loc.getFilename())); 740 741 // Search for a matching expected diagnostic. 742 // If we find something that is close then emit a more specific error. 743 ExpectedDiag *nearMiss = nullptr; 744 745 // If this was an expected error, remember that we saw it and return. 746 unsigned line = loc.getLine(); 747 for (auto &e : *diags) { 748 if (line == e.lineNo && msg.contains(e.substring)) { 749 if (e.kind == kind) { 750 e.matched = true; 751 return; 752 } 753 754 // If this only differs based on the diagnostic kind, then consider it 755 // to be a near miss. 756 nearMiss = &e; 757 } 758 } 759 760 // Otherwise, emit an error for the near miss. 761 if (nearMiss) 762 mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error, 763 "'" + getDiagKindStr(kind) + 764 "' diagnostic emitted when expecting a '" + 765 getDiagKindStr(nearMiss->kind) + "'"); 766 else 767 emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg, 768 DiagnosticSeverity::Error); 769 impl->status = failure(); 770 } 771 772 //===----------------------------------------------------------------------===// 773 // ParallelDiagnosticHandler 774 //===----------------------------------------------------------------------===// 775 776 namespace mlir { 777 namespace detail { 778 struct ParallelDiagnosticHandlerImpl : public llvm::PrettyStackTraceEntry { 779 struct ThreadDiagnostic { 780 ThreadDiagnostic(size_t id, Diagnostic diag) 781 : id(id), diag(std::move(diag)) {} 782 bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; } 783 784 /// The id for this diagnostic, this is used for ordering. 785 /// Note: This id corresponds to the ordered position of the current element 786 /// being processed by a given thread. 787 size_t id; 788 789 /// The diagnostic. 790 Diagnostic diag; 791 }; 792 793 ParallelDiagnosticHandlerImpl(MLIRContext *ctx) : handlerID(0), context(ctx) { 794 handlerID = ctx->getDiagEngine().registerHandler([this](Diagnostic &diag) { 795 uint64_t tid = llvm::get_threadid(); 796 llvm::sys::SmartScopedLock<true> lock(mutex); 797 798 // If this thread is not tracked, then return failure to let another 799 // handler process this diagnostic. 800 if (!threadToOrderID.count(tid)) 801 return failure(); 802 803 // Append a new diagnostic. 804 diagnostics.emplace_back(threadToOrderID[tid], std::move(diag)); 805 return success(); 806 }); 807 } 808 809 ~ParallelDiagnosticHandlerImpl() override { 810 // Erase this handler from the context. 811 context->getDiagEngine().eraseHandler(handlerID); 812 813 // Early exit if there are no diagnostics, this is the common case. 814 if (diagnostics.empty()) 815 return; 816 817 // Emit the diagnostics back to the context. 818 emitDiagnostics([&](Diagnostic diag) { 819 return context->getDiagEngine().emit(std::move(diag)); 820 }); 821 } 822 823 /// Utility method to emit any held diagnostics. 824 void emitDiagnostics(std::function<void(Diagnostic)> emitFn) const { 825 // Stable sort all of the diagnostics that were emitted. This creates a 826 // deterministic ordering for the diagnostics based upon which order id they 827 // were emitted for. 828 std::stable_sort(diagnostics.begin(), diagnostics.end()); 829 830 // Emit each diagnostic to the context again. 831 for (ThreadDiagnostic &diag : diagnostics) 832 emitFn(std::move(diag.diag)); 833 } 834 835 /// Set the order id for the current thread. 836 void setOrderIDForThread(size_t orderID) { 837 uint64_t tid = llvm::get_threadid(); 838 llvm::sys::SmartScopedLock<true> lock(mutex); 839 threadToOrderID[tid] = orderID; 840 } 841 842 /// Remove the order id for the current thread. 843 void eraseOrderIDForThread() { 844 uint64_t tid = llvm::get_threadid(); 845 llvm::sys::SmartScopedLock<true> lock(mutex); 846 threadToOrderID.erase(tid); 847 } 848 849 /// Dump the current diagnostics that were inflight. 850 void print(raw_ostream &os) const override { 851 // Early exit if there are no diagnostics, this is the common case. 852 if (diagnostics.empty()) 853 return; 854 855 os << "In-Flight Diagnostics:\n"; 856 emitDiagnostics([&](Diagnostic diag) { 857 os.indent(4); 858 859 // Print each diagnostic with the format: 860 // "<location>: <kind>: <msg>" 861 if (!diag.getLocation().isa<UnknownLoc>()) 862 os << diag.getLocation() << ": "; 863 switch (diag.getSeverity()) { 864 case DiagnosticSeverity::Error: 865 os << "error: "; 866 break; 867 case DiagnosticSeverity::Warning: 868 os << "warning: "; 869 break; 870 case DiagnosticSeverity::Note: 871 os << "note: "; 872 break; 873 case DiagnosticSeverity::Remark: 874 os << "remark: "; 875 break; 876 } 877 os << diag << '\n'; 878 }); 879 } 880 881 /// A smart mutex to lock access to the internal state. 882 llvm::sys::SmartMutex<true> mutex; 883 884 /// A mapping between the thread id and the current order id. 885 DenseMap<uint64_t, size_t> threadToOrderID; 886 887 /// An unordered list of diagnostics that were emitted. 888 mutable std::vector<ThreadDiagnostic> diagnostics; 889 890 /// The unique id for the parallel handler. 891 DiagnosticEngine::HandlerID handlerID; 892 893 /// The context to emit the diagnostics to. 894 MLIRContext *context; 895 }; 896 } // end namespace detail 897 } // end namespace mlir 898 899 ParallelDiagnosticHandler::ParallelDiagnosticHandler(MLIRContext *ctx) 900 : impl(new ParallelDiagnosticHandlerImpl(ctx)) {} 901 ParallelDiagnosticHandler::~ParallelDiagnosticHandler() {} 902 903 /// Set the order id for the current thread. 904 void ParallelDiagnosticHandler::setOrderIDForThread(size_t orderID) { 905 impl->setOrderIDForThread(orderID); 906 } 907 908 /// Remove the order id for the current thread. This removes the thread from 909 /// diagnostics tracking. 910 void ParallelDiagnosticHandler::eraseOrderIDForThread() { 911 impl->eraseOrderIDForThread(); 912 } 913