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