//===- Diagnostics.h - MLIR Diagnostics -------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file defines utilities for emitting diagnostics. // //===----------------------------------------------------------------------===// #ifndef MLIR_IR_DIAGNOSTICS_H #define MLIR_IR_DIAGNOSTICS_H #include "mlir/IR/Location.h" #include #include namespace llvm { class MemoryBuffer; class SMLoc; class SourceMgr; } // namespace llvm namespace mlir { class DiagnosticEngine; class MLIRContext; class Operation; class OperationName; class OpPrintingFlags; class OpWithFlags; class Type; class Value; namespace detail { struct DiagnosticEngineImpl; } // namespace detail /// Defines the different supported severity of a diagnostic. enum class DiagnosticSeverity { Note, Warning, Error, Remark, }; //===----------------------------------------------------------------------===// // DiagnosticArgument //===----------------------------------------------------------------------===// /// A variant type that holds a single argument for a diagnostic. class DiagnosticArgument { public: /// Note: The constructors below are only exposed due to problems accessing /// constructors from type traits, they should not be used directly by users. // Construct from an Attribute. explicit DiagnosticArgument(Attribute attr); // Construct from a floating point number. explicit DiagnosticArgument(double val) : kind(DiagnosticArgumentKind::Double), doubleVal(val) {} explicit DiagnosticArgument(float val) : DiagnosticArgument(double(val)) {} // Construct from a signed integer. template explicit DiagnosticArgument( T val, std::enable_if_t::value && std::numeric_limits::is_integer && sizeof(T) <= sizeof(int64_t)> * = nullptr) : kind(DiagnosticArgumentKind::Integer), opaqueVal(int64_t(val)) {} // Construct from an unsigned integer. template explicit DiagnosticArgument( T val, std::enable_if_t::value && std::numeric_limits::is_integer && sizeof(T) <= sizeof(uint64_t)> * = nullptr) : kind(DiagnosticArgumentKind::Unsigned), opaqueVal(uint64_t(val)) {} // Construct from a string reference. explicit DiagnosticArgument(StringRef val) : kind(DiagnosticArgumentKind::String), stringVal(val) {} // Construct from a Type. explicit DiagnosticArgument(Type val); /// Enum that represents the different kinds of diagnostic arguments /// supported. enum class DiagnosticArgumentKind { Attribute, Double, Integer, String, Type, Unsigned, }; /// Outputs this argument to a stream. void print(raw_ostream &os) const; /// Returns the kind of this argument. DiagnosticArgumentKind getKind() const { return kind; } /// Returns this argument as an Attribute. Attribute getAsAttribute() const; /// Returns this argument as a double. double getAsDouble() const { assert(getKind() == DiagnosticArgumentKind::Double); return doubleVal; } /// Returns this argument as a signed integer. int64_t getAsInteger() const { assert(getKind() == DiagnosticArgumentKind::Integer); return static_cast(opaqueVal); } /// Returns this argument as a string. StringRef getAsString() const { assert(getKind() == DiagnosticArgumentKind::String); return stringVal; } /// Returns this argument as a Type. Type getAsType() const; /// Returns this argument as an unsigned integer. uint64_t getAsUnsigned() const { assert(getKind() == DiagnosticArgumentKind::Unsigned); return static_cast(opaqueVal); } private: friend class Diagnostic; /// The kind of this argument. DiagnosticArgumentKind kind; /// The value of this argument. union { double doubleVal; intptr_t opaqueVal; StringRef stringVal; }; }; inline raw_ostream &operator<<(raw_ostream &os, const DiagnosticArgument &arg) { arg.print(os); return os; } //===----------------------------------------------------------------------===// // Diagnostic //===----------------------------------------------------------------------===// /// This class contains all of the information necessary to report a diagnostic /// to the DiagnosticEngine. It should generally not be constructed directly, /// and instead used transitively via InFlightDiagnostic. class Diagnostic { using NoteVector = std::vector>; public: Diagnostic(Location loc, DiagnosticSeverity severity) : loc(loc), severity(severity) {} Diagnostic(Diagnostic &&) = default; Diagnostic &operator=(Diagnostic &&) = default; /// Returns the severity of this diagnostic. DiagnosticSeverity getSeverity() const { return severity; } /// Returns the source location for this diagnostic. Location getLocation() const { return loc; } /// Returns the current list of diagnostic arguments. MutableArrayRef getArguments() { return arguments; } ArrayRef getArguments() const { return arguments; } /// Stream operator for inserting new diagnostic arguments. template std::enable_if_t::value && std::is_constructible::value, Diagnostic &> operator<<(Arg &&val) { arguments.push_back(DiagnosticArgument(std::forward(val))); return *this; } Diagnostic &operator<<(StringAttr val); /// Stream in a string literal. template Diagnostic &operator<<(const char (&val)[n]) { arguments.push_back(DiagnosticArgument(val)); return *this; } /// Stream in a Twine argument. Diagnostic &operator<<(char val); Diagnostic &operator<<(const Twine &val); Diagnostic &operator<<(Twine &&val); /// Stream in an OperationName. Diagnostic &operator<<(OperationName val); /// Stream in an Operation. Diagnostic &operator<<(Operation &op); Diagnostic &operator<<(OpWithFlags op); Diagnostic &operator<<(Operation *op) { return *this << *op; } /// Append an operation with the given printing flags. Diagnostic &appendOp(Operation &op, const OpPrintingFlags &flags); /// Stream in a Value. Diagnostic &operator<<(Value val); /// Stream in an enum that has a `stringifyEnum` function. template std::enable_if_t< std::is_enum_v && std::is_convertible_v())), StringRef>, Diagnostic &> operator<<(EnumT val) { return *this << stringifyEnum(val); } /// Stream in a range. template > std::enable_if_t::value, Diagnostic &> operator<<(T &&range) { return appendRange(range); } /// Append a range to the diagnostic. The default delimiter between elements /// is ','. template Diagnostic &appendRange(const T &c, const char *delim = ", ") { llvm::interleave( c, [this](const auto &a) { *this << a; }, [&]() { *this << delim; }); return *this; } /// Append arguments to the diagnostic. template Diagnostic &append(Arg1 &&arg1, Arg2 &&arg2, Args &&...args) { append(std::forward(arg1)); return append(std::forward(arg2), std::forward(args)...); } /// Append one argument to the diagnostic. template Diagnostic &append(Arg &&arg) { *this << std::forward(arg); return *this; } /// Outputs this diagnostic to a stream. void print(raw_ostream &os) const; /// Converts the diagnostic to a string. std::string str() const; /// Attaches a note to this diagnostic. A new location may be optionally /// provided, if not, then the location defaults to the one specified for this /// diagnostic. Notes may not be attached to other notes. Diagnostic &attachNote(std::optional noteLoc = std::nullopt); using note_iterator = llvm::pointee_iterator; using const_note_iterator = llvm::pointee_iterator; /// Returns the notes held by this diagnostic. iterator_range getNotes() { return llvm::make_pointee_range(notes); } iterator_range getNotes() const { return llvm::make_pointee_range(notes); } /// Allow a diagnostic to be converted to 'failure'. operator LogicalResult() const; /// Allow a diagnostic to be converted to 'failure'. operator ParseResult() const { return ParseResult(LogicalResult(*this)); } /// Allow a diagnostic to be converted to FailureOr. Always results in /// 'failure' because this cast cannot possibly return an object of 'T'. template operator FailureOr() const { return failure(); } /// Returns the current list of diagnostic metadata. SmallVectorImpl &getMetadata() { return metadata; } private: Diagnostic(const Diagnostic &rhs) = delete; Diagnostic &operator=(const Diagnostic &rhs) = delete; /// The source location. Location loc; /// The severity of this diagnostic. DiagnosticSeverity severity; /// The current list of arguments. SmallVector arguments; /// A list of string values used as arguments. This is used to guarantee the /// liveness of non-constant strings used in diagnostics. std::vector> strings; /// A list of attached notes. NoteVector notes; /// A list of metadata attached to this Diagnostic. SmallVector metadata; }; inline raw_ostream &operator<<(raw_ostream &os, const Diagnostic &diag) { diag.print(os); return os; } //===----------------------------------------------------------------------===// // InFlightDiagnostic //===----------------------------------------------------------------------===// /// This class represents a diagnostic that is inflight and set to be reported. /// This allows for last minute modifications of the diagnostic before it is /// emitted by a DiagnosticEngine. class InFlightDiagnostic { public: InFlightDiagnostic() = default; InFlightDiagnostic(InFlightDiagnostic &&rhs) : owner(rhs.owner), impl(std::move(rhs.impl)) { // Reset the rhs diagnostic. rhs.impl.reset(); rhs.abandon(); } ~InFlightDiagnostic() { if (isInFlight()) report(); } /// Stream operator for new diagnostic arguments. template InFlightDiagnostic &operator<<(Arg &&arg) & { return append(std::forward(arg)); } template InFlightDiagnostic &&operator<<(Arg &&arg) && { return std::move(append(std::forward(arg))); } /// Append arguments to the diagnostic. template InFlightDiagnostic &append(Args &&...args) & { assert(isActive() && "diagnostic not active"); if (isInFlight()) impl->append(std::forward(args)...); return *this; } template InFlightDiagnostic &&append(Args &&...args) && { return std::move(append(std::forward(args)...)); } /// Attaches a note to this diagnostic. Diagnostic &attachNote(std::optional noteLoc = std::nullopt) { assert(isActive() && "diagnostic not active"); return impl->attachNote(noteLoc); } /// Returns the underlying diagnostic or nullptr if this diagnostic isn't /// active. Diagnostic *getUnderlyingDiagnostic() { return impl ? &*impl : nullptr; } /// Reports the diagnostic to the engine. void report(); /// Abandons this diagnostic so that it will no longer be reported. void abandon(); /// Allow an inflight diagnostic to be converted to 'failure', otherwise /// 'success' if this is an empty diagnostic. operator LogicalResult() const; /// Allow an inflight diagnostic to be converted to 'failure', otherwise /// 'success' if this is an empty diagnostic. operator ParseResult() const { return ParseResult(LogicalResult(*this)); } /// Allow an inflight diagnostic to be converted to FailureOr. Always /// results in 'failure' because this cast cannot possibly return an object of /// 'T'. template operator FailureOr() const { return failure(); } private: InFlightDiagnostic &operator=(const InFlightDiagnostic &) = delete; InFlightDiagnostic &operator=(InFlightDiagnostic &&) = delete; InFlightDiagnostic(DiagnosticEngine *owner, Diagnostic &&rhs) : owner(owner), impl(std::move(rhs)) {} /// Returns true if the diagnostic is still active, i.e. it has a live /// diagnostic. bool isActive() const { return impl.has_value(); } /// Returns true if the diagnostic is still in flight to be reported. bool isInFlight() const { return owner; } // Allow access to the constructor. friend DiagnosticEngine; /// The engine that this diagnostic is to report to. DiagnosticEngine *owner = nullptr; /// The raw diagnostic that is inflight to be reported. std::optional impl; }; //===----------------------------------------------------------------------===// // DiagnosticEngine //===----------------------------------------------------------------------===// /// This class is the main interface for diagnostics. The DiagnosticEngine /// manages the registration of diagnostic handlers as well as the core API for /// diagnostic emission. This class should not be constructed directly, but /// instead interfaced with via an MLIRContext instance. class DiagnosticEngine { public: ~DiagnosticEngine(); // Diagnostic handler registration and use. MLIR supports the ability for the // IR to carry arbitrary metadata about operation location information. If a // problem is detected by the compiler, it can invoke the emitError / // emitWarning / emitRemark method on an Operation and have it get reported // through this interface. // // Tools using MLIR are encouraged to register error handlers and define a // schema for their location information. If they don't, then warnings and // notes will be dropped and errors will be emitted to errs. /// The handler type for MLIR diagnostics. This function takes a diagnostic as /// input, and returns success if the handler has fully processed this /// diagnostic. Returns failure otherwise. using HandlerTy = llvm::unique_function; /// A handle to a specific registered handler object. using HandlerID = uint64_t; /// Register a new handler for diagnostics to the engine. Diagnostics are /// process by handlers in stack-like order, meaning that the last added /// handlers will process diagnostics first. This function returns a unique /// identifier for the registered handler, which can be used to unregister /// this handler at a later time. HandlerID registerHandler(HandlerTy handler); /// Set the diagnostic handler with a function that returns void. This is a /// convenient wrapper for handlers that always completely process the given /// diagnostic. template ()( std::declval()))> std::enable_if_t::value, HandlerID> registerHandler(FuncTy &&handler) { return registerHandler([=](Diagnostic &diag) { handler(diag); return success(); }); } /// Erase the registered diagnostic handler with the given identifier. void eraseHandler(HandlerID id); /// Create a new inflight diagnostic with the given location and severity. InFlightDiagnostic emit(Location loc, DiagnosticSeverity severity) { assert(severity != DiagnosticSeverity::Note && "notes should not be emitted directly"); return InFlightDiagnostic(this, Diagnostic(loc, severity)); } /// Emit a diagnostic using the registered issue handler if present, or with /// the default behavior if not. The diagnostic instance is consumed in the /// process. void emit(Diagnostic &&diag); private: friend class MLIRContextImpl; DiagnosticEngine(); /// The internal implementation of the DiagnosticEngine. std::unique_ptr impl; }; /// Utility method to emit an error message using this location. InFlightDiagnostic emitError(Location loc); InFlightDiagnostic emitError(Location loc, const Twine &message); /// Utility method to emit a warning message using this location. InFlightDiagnostic emitWarning(Location loc); InFlightDiagnostic emitWarning(Location loc, const Twine &message); /// Utility method to emit a remark message using this location. InFlightDiagnostic emitRemark(Location loc); InFlightDiagnostic emitRemark(Location loc, const Twine &message); /// Overloads of the above emission functions that take an optionally null /// location. If the location is null, no diagnostic is emitted and a failure is /// returned. Given that the provided location may be null, these methods take /// the diagnostic arguments directly instead of relying on the returned /// InFlightDiagnostic. template LogicalResult emitOptionalError(std::optional loc, Args &&...args) { if (loc) return emitError(*loc).append(std::forward(args)...); return failure(); } template LogicalResult emitOptionalWarning(std::optional loc, Args &&...args) { if (loc) return emitWarning(*loc).append(std::forward(args)...); return failure(); } template LogicalResult emitOptionalRemark(std::optional loc, Args &&...args) { if (loc) return emitRemark(*loc).append(std::forward(args)...); return failure(); } //===----------------------------------------------------------------------===// // ScopedDiagnosticHandler //===----------------------------------------------------------------------===// /// This diagnostic handler is a simple RAII class that registers and erases a /// diagnostic handler on a given context. This class can be either be used /// directly, or in conjunction with a derived diagnostic handler. class ScopedDiagnosticHandler { public: explicit ScopedDiagnosticHandler(MLIRContext *ctx) : handlerID(0), ctx(ctx) {} template ScopedDiagnosticHandler(MLIRContext *ctx, FuncTy &&handler) : handlerID(0), ctx(ctx) { setHandler(std::forward(handler)); } ~ScopedDiagnosticHandler(); protected: /// Set the handler to manage via RAII. template void setHandler(FuncTy &&handler) { auto &diagEngine = ctx->getDiagEngine(); if (handlerID) diagEngine.eraseHandler(handlerID); handlerID = diagEngine.registerHandler(std::forward(handler)); } private: /// The unique id for the scoped handler. DiagnosticEngine::HandlerID handlerID; /// The context to erase the handler from. MLIRContext *ctx; }; //===----------------------------------------------------------------------===// // SourceMgrDiagnosticHandler //===----------------------------------------------------------------------===// namespace detail { struct SourceMgrDiagnosticHandlerImpl; } // namespace detail /// This class is a utility diagnostic handler for use with llvm::SourceMgr. class SourceMgrDiagnosticHandler : public ScopedDiagnosticHandler { public: /// This type represents a functor used to filter out locations when printing /// a diagnostic. It should return true if the provided location is okay to /// display, false otherwise. If all locations in a diagnostic are filtered /// out, the first location is used as the sole location. When deciding /// whether or not to filter a location, this function should not recurse into /// any nested location. This recursion is handled automatically by the /// caller. using ShouldShowLocFn = llvm::unique_function; SourceMgrDiagnosticHandler(llvm::SourceMgr &mgr, MLIRContext *ctx, raw_ostream &os, ShouldShowLocFn &&shouldShowLocFn = {}); SourceMgrDiagnosticHandler(llvm::SourceMgr &mgr, MLIRContext *ctx, ShouldShowLocFn &&shouldShowLocFn = {}); ~SourceMgrDiagnosticHandler(); /// Emit the given diagnostic information with the held source manager. void emitDiagnostic(Location loc, Twine message, DiagnosticSeverity kind, bool displaySourceLine = true); /// Set the maximum depth that a call stack will be printed. Defaults to 10. void setCallStackLimit(unsigned limit); protected: /// Emit the given diagnostic with the held source manager. void emitDiagnostic(Diagnostic &diag); /// Get a memory buffer for the given file, or nullptr if no file is /// available. const llvm::MemoryBuffer *getBufferForFile(StringRef filename); /// The source manager that we are wrapping. llvm::SourceMgr &mgr; /// The output stream to use when printing diagnostics. raw_ostream &os; /// A functor used when determining if a location for a diagnostic should be /// shown. If null, all locations should be shown. ShouldShowLocFn shouldShowLocFn; private: /// Convert a location into the given memory buffer into an SMLoc. SMLoc convertLocToSMLoc(FileLineColLoc loc); /// Given a location, returns the first nested location (including 'loc') that /// can be shown to the user. std::optional findLocToShow(Location loc); /// The maximum depth that a call stack will be printed. unsigned callStackLimit = 10; std::unique_ptr impl; }; //===----------------------------------------------------------------------===// // SourceMgrDiagnosticVerifierHandler //===----------------------------------------------------------------------===// namespace detail { struct SourceMgrDiagnosticVerifierHandlerImpl; } // namespace detail /// This class is a utility diagnostic handler for use with llvm::SourceMgr that /// verifies that emitted diagnostics match 'expected-*' lines on the /// corresponding line of the source file. class SourceMgrDiagnosticVerifierHandler : public SourceMgrDiagnosticHandler { public: enum class Level { None = 0, All, OnlyExpected }; SourceMgrDiagnosticVerifierHandler(llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level = Level::All); SourceMgrDiagnosticVerifierHandler(llvm::SourceMgr &srcMgr, MLIRContext *ctx, Level level = Level::All); ~SourceMgrDiagnosticVerifierHandler(); /// Returns the status of the handler and verifies that all expected /// diagnostics were emitted. This return success if all diagnostics were /// verified correctly, failure otherwise. LogicalResult verify(); /// Register this handler with the given context. This is intended for use /// with the splitAndProcessBuffer function. void registerInContext(MLIRContext *ctx); private: /// Process a single diagnostic. void process(Diagnostic &diag); /// Process a LocationAttr diagnostic. void process(LocationAttr loc, StringRef msg, DiagnosticSeverity kind); std::unique_ptr impl; }; //===----------------------------------------------------------------------===// // ParallelDiagnosticHandler //===----------------------------------------------------------------------===// namespace detail { struct ParallelDiagnosticHandlerImpl; } // namespace detail /// This class is a utility diagnostic handler for use when multi-threading some /// part of the compiler where diagnostics may be emitted. This handler ensures /// a deterministic ordering to the emitted diagnostics that mirrors that of a /// single-threaded compilation. class ParallelDiagnosticHandler { public: ParallelDiagnosticHandler(MLIRContext *ctx); ~ParallelDiagnosticHandler(); /// Set the order id for the current thread. This is required to be set by /// each thread that will be emitting diagnostics to this handler. The orderID /// corresponds to the order in which diagnostics would be emitted when /// executing synchronously. For example, if we were processing a list /// of operations [a, b, c] on a single-thread. Diagnostics emitted while /// processing operation 'a' would be emitted before those for 'b' or 'c'. /// This corresponds 1-1 with the 'orderID'. The thread that is processing 'a' /// should set the orderID to '0'; the thread processing 'b' should set it to /// '1'; and so on and so forth. This provides a way for the handler to /// deterministically order the diagnostics that it receives given the thread /// that it is receiving on. void setOrderIDForThread(size_t orderID); /// Remove the order id for the current thread. This removes the thread from /// diagnostics tracking. void eraseOrderIDForThread(); private: std::unique_ptr impl; }; } // namespace mlir #endif