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