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