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