diff --git a/include/llvm-c/Remarks.h b/include/llvm-c/Remarks.h index 901acac76d9..e57ca3cd8f5 100644 --- a/include/llvm-c/Remarks.h +++ b/include/llvm-c/Remarks.h @@ -33,87 +33,202 @@ extern "C" { #define REMARKS_API_VERSION 0 +/** + * The type of the emitted remark. + */ +enum LLVMRemarkType { + LLVMRemarkTypeUnknown, + LLVMRemarkTypePassed, + LLVMRemarkTypeMissed, + LLVMRemarkTypeAnalysis, + LLVMRemarkTypeAnalysisFPCommute, + LLVMRemarkTypeAnalysisAliasing, + LLVMRemarkTypeFailure +}; + /** * String containing a buffer and a length. The buffer is not guaranteed to be * zero-terminated. * * \since REMARKS_API_VERSION=0 */ -typedef struct { - const char *Str; - uint32_t Len; -} LLVMRemarkStringRef; +typedef struct LLVMRemarkOpaqueString *LLVMRemarkStringRef; + +/** + * Returns the buffer holding the string. + * + * \since REMARKS_API_VERSION=0 + */ +extern const char *LLVMRemarkStringGetData(LLVMRemarkStringRef String); + +/** + * Returns the size of the string. + * + * \since REMARKS_API_VERSION=0 + */ +extern uint32_t LLVMRemarkStringGetLen(LLVMRemarkStringRef String); /** * DebugLoc containing File, Line and Column. * * \since REMARKS_API_VERSION=0 */ -typedef struct { - // File: - LLVMRemarkStringRef SourceFile; - // Line: - uint32_t SourceLineNumber; - // Column: - uint32_t SourceColumnNumber; -} LLVMRemarkDebugLoc; +typedef struct LLVMRemarkOpaqueDebugLoc *LLVMRemarkDebugLocRef; + +/** + * Return the path to the source file for a debug location. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkStringRef +LLVMRemarkDebugLocGetSourceFilePath(LLVMRemarkDebugLocRef DL); + +/** + * Return the line in the source file for a debug location. + * + * \since REMARKS_API_VERSION=0 + */ +extern uint32_t LLVMRemarkDebugLocGetSourceLine(LLVMRemarkDebugLocRef DL); + +/** + * Return the column in the source file for a debug location. + * + * \since REMARKS_API_VERSION=0 + */ +extern uint32_t LLVMRemarkDebugLocGetSourceColumn(LLVMRemarkDebugLocRef DL); /** * Element of the "Args" list. The key might give more information about what - * are the semantics of the value, e.g. "Callee" will tell you that the value + * the semantics of the value are, e.g. "Callee" will tell you that the value * is a symbol that names a function. * * \since REMARKS_API_VERSION=0 */ -typedef struct { - // e.g. "Callee" - LLVMRemarkStringRef Key; - // e.g. "malloc" - LLVMRemarkStringRef Value; - - // "DebugLoc": Optional - LLVMRemarkDebugLoc DebugLoc; -} LLVMRemarkArg; +typedef struct LLVMRemarkOpaqueArg *LLVMRemarkArgRef; /** - * One remark entry. + * Returns the key of an argument. The key defines what the value is, and the + * same key can appear multiple times in the list of arguments. * * \since REMARKS_API_VERSION=0 */ -typedef struct { - // e.g. !Missed, !Passed - LLVMRemarkStringRef RemarkType; - // "Pass": Required - LLVMRemarkStringRef PassName; - // "Name": Required - LLVMRemarkStringRef RemarkName; - // "Function": Required - LLVMRemarkStringRef FunctionName; +extern LLVMRemarkStringRef LLVMRemarkArgGetKey(LLVMRemarkArgRef Arg); - // "DebugLoc": Optional - LLVMRemarkDebugLoc DebugLoc; - // "Hotness": Optional - uint32_t Hotness; - // "Args": Optional. It is an array of `num_args` elements. - uint32_t NumArgs; - LLVMRemarkArg *Args; -} LLVMRemarkEntry; +/** + * Returns the value of an argument. This is a string that can contain newlines. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkStringRef LLVMRemarkArgGetValue(LLVMRemarkArgRef Arg); + +/** + * Returns the debug location that is attached to the value of this argument. + * + * If there is no debug location, the return value will be `NULL`. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkDebugLocRef LLVMRemarkArgGetDebugLoc(LLVMRemarkArgRef Arg); + +/** + * A remark emitted by the compiler. + * + * \since REMARKS_API_VERSION=0 + */ +typedef struct LLVMRemarkOpaqueEntry *LLVMRemarkEntryRef; + +/** + * The type of the remark. For example, it can allow users to only keep the + * missed optimizations from the compiler. + * + * \since REMARKS_API_VERSION=0 + */ +extern enum LLVMRemarkType LLVMRemarkEntryGetType(LLVMRemarkEntryRef Remark); + +/** + * Get the name of the pass that emitted this remark. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkStringRef +LLVMRemarkEntryGetPassName(LLVMRemarkEntryRef Remark); + +/** + * Get an identifier of the remark. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkStringRef +LLVMRemarkEntryGetRemarkName(LLVMRemarkEntryRef Remark); + +/** + * Get the name of the function being processsed when the remark was emitted. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkStringRef +LLVMRemarkEntryGetFunctionName(LLVMRemarkEntryRef Remark); + +/** + * Returns the debug location that is attached to this remark. + * + * If there is no debug location, the return value will be `NULL`. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkDebugLocRef +LLVMRemarkEntryGetDebugLoc(LLVMRemarkEntryRef Remark); + +/** + * Return the hotness of the remark. + * + * A hotness of `0` means this value is not set. + * + * \since REMARKS_API_VERSION=0 + */ +extern uint64_t LLVMRemarkEntryGetHotness(LLVMRemarkEntryRef Remark); + +/** + * The number of arguments the remark holds. + * + * \since REMARKS_API_VERSION=0 + */ +extern uint32_t LLVMRemarkEntryGetNumArgs(LLVMRemarkEntryRef Remark); + +/** + * Get a new iterator to iterate over a remark's argument. + * + * If there are no arguments in \p Remark, the return value will be `NULL`. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkArgRef LLVMRemarkEntryGetFirstArg(LLVMRemarkEntryRef Remark); + +/** + * Get the next argument in \p Remark from the position of \p It. + * + * Returns `NULL` if there are no more arguments available. + * + * \since REMARKS_API_VERSION=0 + */ +extern LLVMRemarkArgRef LLVMRemarkEntryGetNextArg(LLVMRemarkArgRef It, + LLVMRemarkEntryRef Remark); typedef struct LLVMRemarkOpaqueParser *LLVMRemarkParserRef; /** - * Creates a remark parser that can be used to read and parse the buffer located - * in \p Buf of size \p Size. + * Creates a remark parser that can be used to parse the buffer located in \p + * Buf of size \p Size bytes. * - * \p Buf cannot be NULL. + * \p Buf cannot be `NULL`. * * This function should be paired with LLVMRemarkParserDispose() to avoid * leaking resources. * * \since REMARKS_API_VERSION=0 */ -extern LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf, - uint64_t Size); +extern LLVMRemarkParserRef LLVMRemarkParserCreateYAML(const void *Buf, + uint64_t Size); /** * Returns the next remark in the file. @@ -121,9 +236,9 @@ extern LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf, * The value pointed to by the return value is invalidated by the next call to * LLVMRemarkParserGetNext(). * - * If the parser reaches the end of the buffer, the return value will be NULL. + * If the parser reaches the end of the buffer, the return value will be `NULL`. * - * In the case of an error, the return value will be NULL, and: + * In the case of an error, the return value will be `NULL`, and: * * 1) LLVMRemarkParserHasError() will return `1`. * @@ -134,18 +249,16 @@ extern LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf, * * 1) An argument is invalid. * - * 2) There is a YAML parsing error. This type of error aborts parsing - * immediately and returns `1`. It can occur on malformed YAML. + * 2) There is a parsing error. This can occur on things like malformed YAML. * - * 3) Remark parsing error. If this type of error occurs, the parser won't call - * the handler and will continue to the next one. It can occur on malformed - * remarks, like missing or extra fields in the file. + * 3) There is a Remark semantic error. This can occur on well-formed files with + * missing or extra fields. * * Here is a quick example of the usage: * * ``` - * LLVMRemarkParserRef Parser = LLVMRemarkParserCreate(Buf, Size); - * LLVMRemarkEntry *Remark = NULL; + * LLVMRemarkParserRef Parser = LLVMRemarkParserCreateYAML(Buf, Size); + * LLVMRemarkEntryRef Remark = NULL; * while ((Remark == LLVMRemarkParserGetNext(Parser))) { * // use Remark * } @@ -155,7 +268,7 @@ extern LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf, * * \since REMARKS_API_VERSION=0 */ -extern LLVMRemarkEntry *LLVMRemarkParserGetNext(LLVMRemarkParserRef Parser); +extern LLVMRemarkEntryRef LLVMRemarkParserGetNext(LLVMRemarkParserRef Parser); /** * Returns `1` if the parser encountered an error while parsing the buffer. @@ -185,7 +298,7 @@ extern const char *LLVMRemarkParserGetErrorMessage(LLVMRemarkParserRef Parser); extern void LLVMRemarkParserDispose(LLVMRemarkParserRef Parser); /** - * Returns the version of the remarks dylib. + * Returns the version of the remarks library. * * \since REMARKS_API_VERSION=0 */ diff --git a/include/llvm/Remarks/Remark.h b/include/llvm/Remarks/Remark.h new file mode 100644 index 00000000000..67fa1718808 --- /dev/null +++ b/include/llvm/Remarks/Remark.h @@ -0,0 +1,98 @@ +//===-- llvm/Remarks/Remark.h - The remark type -----------------*- 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 an abstraction for handling remarks. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_REMARKS_REMARK_H +#define LLVM_REMARKS_REMARK_H + +#include "llvm-c/Remarks.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CBindingWrapping.h" +#include + +namespace llvm { +namespace remarks { + +/// The debug location used to track a remark back to the source file. +struct RemarkLocation { + /// Absolute path of the source file corresponding to this remark. + StringRef SourceFilePath; + unsigned SourceLine; + unsigned SourceColumn; +}; + +// Create wrappers for C Binding types (see CBindingWrapping.h). +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(RemarkLocation, LLVMRemarkDebugLocRef) + +/// A key-value pair with a debug location that is used to display the remarks +/// at the right place in the source. +struct Argument { + StringRef Key; + // FIXME: We might want to be able to store other types than strings here. + StringRef Val; + // If set, the debug location corresponding to the value. + Optional Loc; +}; + +// Create wrappers for C Binding types (see CBindingWrapping.h). +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(Argument, LLVMRemarkArgRef) + +/// The type of the remark. +enum class Type { + Unknown, + Passed, + Missed, + Analysis, + AnalysisFPCommute, + AnalysisAliasing, + Failure, + LastTypeValue = Failure +}; + +/// A remark type used for both emission and parsing. +struct Remark { + /// The type of the remark. + enum Type RemarkType = Type::Unknown; + + /// Name of the pass that triggers the emission of this remark. + StringRef PassName; + + /// Textual identifier for the remark (single-word, camel-case). Can be used + /// by external tools reading the output file for remarks to identify the + /// remark. + StringRef RemarkName; + + /// Mangled name of the function that triggers the emssion of this remark. + StringRef FunctionName; + + /// The location in the source file of the remark. + Optional Loc; + + /// If profile information is available, this is the number of times the + /// corresponding code was executed in a profile instrumentation run. + Optional Hotness; + + /// Arguments collected via the streaming interface. + ArrayRef Args; + + /// Return a message composed from the arguments as a string. + std::string getArgsAsMsg() const; +}; + +// Create wrappers for C Binding types (see CBindingWrapping.h). +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(Remark, LLVMRemarkEntryRef) + +} // end namespace remarks +} // end namespace llvm + +#endif /* LLVM_REMARKS_REMARK_H */ diff --git a/include/llvm/Remarks/RemarkParser.h b/include/llvm/Remarks/RemarkParser.h new file mode 100644 index 00000000000..fb8d4c6021a --- /dev/null +++ b/include/llvm/Remarks/RemarkParser.h @@ -0,0 +1,46 @@ +//===-- llvm/Remarks/Remark.h - The remark type -----------------*- 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 provides an interface for parsing remarks in LLVM. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_REMARKS_REMARK_PARSER_H +#define LLVM_REMARKS_REMARK_PARSER_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Remarks/Remark.h" +#include "llvm/Support/Error.h" +#include + +namespace llvm { +namespace remarks { + +struct ParserImpl; + +/// Parser used to parse a raw buffer to remarks::Remark objects. +struct Parser { + /// The hidden implementation of the parser. + std::unique_ptr Impl; + + /// Create a parser parsing \p Buffer to Remark objects. + /// This constructor should be only used for parsing YAML remarks. + Parser(StringRef Buffer); + + // Needed because ParserImpl is an incomplete type. + ~Parser(); + + /// Returns an empty Optional if it reached the end. + /// Returns a valid remark otherwise. + Expected getNext() const; +}; + +} // end namespace remarks +} // end namespace llvm + +#endif /* LLVM_REMARKS_REMARK_PARSER_H */ diff --git a/lib/Remarks/CMakeLists.txt b/lib/Remarks/CMakeLists.txt index 83713d66a60..2ab7e8476a1 100644 --- a/lib/Remarks/CMakeLists.txt +++ b/lib/Remarks/CMakeLists.txt @@ -1,3 +1,5 @@ add_llvm_library(LLVMRemarks + Remark.cpp RemarkParser.cpp + YAMLRemarkParser.cpp ) diff --git a/lib/Remarks/Remark.cpp b/lib/Remarks/Remark.cpp new file mode 100644 index 00000000000..b4be19f47a5 --- /dev/null +++ b/lib/Remarks/Remark.cpp @@ -0,0 +1,128 @@ +//===- Remark.cpp ---------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Implementation of the Remark type and the C API. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Remarks/Remark.h" +#include "llvm-c/Remarks.h" +#include "llvm/Support/CBindingWrapping.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace llvm::remarks; + +std::string Remark::getArgsAsMsg() const { + std::string Str; + raw_string_ostream OS(Str); + for (const Argument &Arg : Args) + OS << Arg.Val; + return OS.str(); +} + +// Create wrappers for C Binding types (see CBindingWrapping.h). +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(StringRef, LLVMRemarkStringRef) + +extern "C" const char *LLVMRemarkStringGetData(LLVMRemarkStringRef String) { + return unwrap(String)->data(); +} + +extern "C" uint32_t LLVMRemarkStringGetLen(LLVMRemarkStringRef String) { + return unwrap(String)->size(); +} + +extern "C" LLVMRemarkStringRef +LLVMRemarkDebugLocGetSourceFilePath(LLVMRemarkDebugLocRef DL) { + return wrap(&unwrap(DL)->SourceFilePath); +} + +extern "C" uint32_t LLVMRemarkDebugLocGetSourceLine(LLVMRemarkDebugLocRef DL) { + return unwrap(DL)->SourceLine; +} + +extern "C" uint32_t +LLVMRemarkDebugLocGetSourceColumn(LLVMRemarkDebugLocRef DL) { + return unwrap(DL)->SourceColumn; +} + +extern "C" LLVMRemarkStringRef LLVMRemarkArgGetKey(LLVMRemarkArgRef Arg) { + return wrap(&unwrap(Arg)->Key); +} + +extern "C" LLVMRemarkStringRef LLVMRemarkArgGetValue(LLVMRemarkArgRef Arg) { + return wrap(&unwrap(Arg)->Val); +} + +extern "C" LLVMRemarkDebugLocRef +LLVMRemarkArgGetDebugLoc(LLVMRemarkArgRef Arg) { + if (const Optional &Loc = unwrap(Arg)->Loc) + return wrap(&*Loc); + return nullptr; +} + +extern "C" LLVMRemarkType LLVMRemarkEntryGetType(LLVMRemarkEntryRef Remark) { + // Assume here that the enums can be converted both ways. + return static_cast(unwrap(Remark)->RemarkType); +} + +extern "C" LLVMRemarkStringRef +LLVMRemarkEntryGetPassName(LLVMRemarkEntryRef Remark) { + return wrap(&unwrap(Remark)->PassName); +} + +extern "C" LLVMRemarkStringRef +LLVMRemarkEntryGetRemarkName(LLVMRemarkEntryRef Remark) { + return wrap(&unwrap(Remark)->RemarkName); +} + +extern "C" LLVMRemarkStringRef +LLVMRemarkEntryGetFunctionName(LLVMRemarkEntryRef Remark) { + return wrap(&unwrap(Remark)->FunctionName); +} + +extern "C" LLVMRemarkDebugLocRef +LLVMRemarkEntryGetDebugLoc(LLVMRemarkEntryRef Remark) { + if (const Optional &Loc = unwrap(Remark)->Loc) + return wrap(&*Loc); + return nullptr; +} + +extern "C" uint64_t LLVMRemarkEntryGetHotness(LLVMRemarkEntryRef Remark) { + if (const Optional &Hotness = unwrap(Remark)->Hotness) + return *Hotness; + return 0; +} + +extern "C" uint32_t LLVMRemarkEntryGetNumArgs(LLVMRemarkEntryRef Remark) { + return unwrap(Remark)->Args.size(); +} + +extern "C" LLVMRemarkArgRef +LLVMRemarkEntryGetFirstArg(LLVMRemarkEntryRef Remark) { + ArrayRef Args = unwrap(Remark)->Args; + // No arguments to iterate on. + if (Args.empty()) + return NULL; + return reinterpret_cast( + const_cast(Args.begin())); +} + +extern "C" LLVMRemarkArgRef +LLVMRemarkEntryGetNextArg(LLVMRemarkArgRef ArgIt, LLVMRemarkEntryRef Remark) { + // No more arguments to iterate on. + if (ArgIt == NULL) + return NULL; + + auto It = (ArrayRef::const_iterator)ArgIt; + auto Next = std::next(It); + if (Next == unwrap(Remark)->Args.end()) + return NULL; + + return reinterpret_cast(const_cast(Next)); +} diff --git a/lib/Remarks/RemarkParser.cpp b/lib/Remarks/RemarkParser.cpp index 204b8002884..30de40dd54a 100644 --- a/lib/Remarks/RemarkParser.cpp +++ b/lib/Remarks/RemarkParser.cpp @@ -11,355 +11,104 @@ // //===----------------------------------------------------------------------===// +#include "llvm/Remarks/RemarkParser.h" +#include "YAMLRemarkParser.h" #include "llvm-c/Remarks.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/Support/SourceMgr.h" -#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/CBindingWrapping.h" using namespace llvm; +using namespace llvm::remarks; -namespace { -struct YAMLRemarkParser { - /// Source manager for better error messages. - SourceMgr SM; - /// Stream for yaml parsing. - yaml::Stream Stream; - /// Storage for the error stream. - std::string ErrorString; - /// The error stream. - raw_string_ostream ErrorStream; - /// Iterator in the YAML stream. - yaml::document_iterator DI; - /// The parsed remark (if any). - Optional LastRemark; - /// Temporary parsing buffer for the arguments. - SmallVector TmpArgs; - /// The state used by the parser to parse a remark entry. Invalidated with - /// every call to `parseYAMLElement`. - struct ParseState { - /// Temporary parsing buffer for the arguments. - SmallVectorImpl *Args; - StringRef Type; - StringRef Pass; - StringRef Name; - StringRef Function; - /// Optional. - Optional File; - Optional Line; - Optional Column; - Optional Hotness; +Parser::Parser(StringRef Buf) : Impl(llvm::make_unique(Buf)) {} - ParseState(SmallVectorImpl &Args) : Args(&Args) {} - /// Use Args only as a **temporary** buffer. - ~ParseState() { Args->clear(); } - }; +Parser::~Parser() = default; - ParseState State; - - /// Set to `true` if we had any errors during parsing. - bool HadAnyErrors = false; - - YAMLRemarkParser(StringRef Buf) - : SM(), Stream(Buf, SM), ErrorString(), ErrorStream(ErrorString), - DI(Stream.begin()), LastRemark(), TmpArgs(), State(TmpArgs) { - SM.setDiagHandler(YAMLRemarkParser::HandleDiagnostic, this); - } - - /// Parse a YAML element. - Error parseYAMLElement(yaml::Document &Remark); - -private: - /// Parse one key to a string. - /// otherwise. - Error parseKey(StringRef &Result, yaml::KeyValueNode &Node); - /// Parse one value to a string. - Error parseValue(StringRef &Result, yaml::KeyValueNode &Node); - /// Parse one value to an unsigned. - Error parseValue(Optional &Result, yaml::KeyValueNode &Node); - /// Parse a debug location. - Error parseDebugLoc(Optional &File, Optional &Line, - Optional &Column, yaml::KeyValueNode &Node); - /// Parse an argument. - Error parseArg(SmallVectorImpl &TmpArgs, yaml::Node &Node); - - /// Handle a diagnostic from the YAML stream. Records the error in the - /// YAMLRemarkParser class. - static void HandleDiagnostic(const SMDiagnostic &Diag, void *Ctx) { - assert(Ctx && "Expected non-null Ctx in diagnostic handler."); - auto *Parser = static_cast(Ctx); - Diag.print(/*ProgName=*/nullptr, Parser->ErrorStream, /*ShowColors*/ false, - /*ShowKindLabels*/ true); - } -}; - -class ParseError : public ErrorInfo { -public: - static char ID; - - ParseError(StringRef Message, yaml::Node &Node) - : Message(Message), Node(Node) {} - - void log(raw_ostream &OS) const override { OS << Message; } - std::error_code convertToErrorCode() const override { - return inconvertibleErrorCode(); - } - - StringRef getMessage() const { return Message; } - yaml::Node &getNode() const { return Node; } - -private: - StringRef Message; // No need to hold a full copy of the buffer. - yaml::Node &Node; -}; - -char ParseError::ID = 0; - -static LLVMRemarkStringRef toRemarkStr(StringRef Str) { - return {Str.data(), static_cast(Str.size())}; -} - -Error YAMLRemarkParser::parseKey(StringRef &Result, yaml::KeyValueNode &Node) { - auto *Key = dyn_cast(Node.getKey()); - if (!Key) - return make_error("key is not a string.", Node); - - Result = Key->getRawValue(); - return Error::success(); -} - -Error YAMLRemarkParser::parseValue(StringRef &Result, - yaml::KeyValueNode &Node) { - auto *Value = dyn_cast(Node.getValue()); - if (!Value) - return make_error("expected a value of scalar type.", Node); - Result = Value->getRawValue(); - - if (Result.front() == '\'') - Result = Result.drop_front(); - - if (Result.back() == '\'') - Result = Result.drop_back(); - - return Error::success(); -} - -Error YAMLRemarkParser::parseValue(Optional &Result, - yaml::KeyValueNode &Node) { - SmallVector Tmp; - auto *Value = dyn_cast(Node.getValue()); - if (!Value) - return make_error("expected a value of scalar type.", Node); - unsigned UnsignedValue = 0; - if (Value->getValue(Tmp).getAsInteger(10, UnsignedValue)) - return make_error("expected a value of integer type.", *Value); - Result = UnsignedValue; - return Error::success(); -} - -Error YAMLRemarkParser::parseDebugLoc(Optional &File, - Optional &Line, - Optional &Column, - yaml::KeyValueNode &Node) { - auto *DebugLoc = dyn_cast(Node.getValue()); - if (!DebugLoc) - return make_error("expected a value of mapping type.", Node); - - for (yaml::KeyValueNode &DLNode : *DebugLoc) { - StringRef KeyName; - if (Error E = parseKey(KeyName, DLNode)) - return E; - if (KeyName == "File") { - File = StringRef(); // Set the optional to contain a default constructed - // value, to be passed to the parsing function. - if (Error E = parseValue(*File, DLNode)) - return E; - } else if (KeyName == "Column") { - if (Error E = parseValue(Column, DLNode)) - return E; - } else if (KeyName == "Line") { - if (Error E = parseValue(Line, DLNode)) - return E; - } else { - return make_error("unknown entry in DebugLoc map.", DLNode); - } - } - - // If any of the debug loc fields is missing, return an error. - if (!File || !Line || !Column) - return make_error("DebugLoc node incomplete.", Node); - - return Error::success(); -} - -Error YAMLRemarkParser::parseArg(SmallVectorImpl &Args, - yaml::Node &Node) { - auto *ArgMap = dyn_cast(&Node); - if (!ArgMap) - return make_error("expected a value of mapping type.", Node); - - StringRef ValueStr; - StringRef KeyStr; - Optional File; - Optional Line; - Optional Column; - - for (yaml::KeyValueNode &ArgEntry : *ArgMap) { - StringRef KeyName; - if (Error E = parseKey(KeyName, ArgEntry)) - return E; - - // Try to parse debug locs. - if (KeyName == "DebugLoc") { - // Can't have multiple DebugLoc entries per argument. - if (File || Line || Column) - return make_error( - "only one DebugLoc entry is allowed per argument.", ArgEntry); - - if (Error E = parseDebugLoc(File, Line, Column, ArgEntry)) - return E; - continue; - } - - // If we already have a string, error out. - if (!ValueStr.empty()) - return make_error( - "only one string entry is allowed per argument.", ArgEntry); - - // Try to parse a string. - if (Error E = parseValue(ValueStr, ArgEntry)) - return E; - - // Keep the key from the string. - KeyStr = KeyName; - } - - if (KeyStr.empty()) - return make_error("argument key is missing.", *ArgMap); - if (ValueStr.empty()) - return make_error("argument value is missing.", *ArgMap); - - Args.push_back(LLVMRemarkArg{ - toRemarkStr(KeyStr), toRemarkStr(ValueStr), - LLVMRemarkDebugLoc{toRemarkStr(File.getValueOr(StringRef())), - Line.getValueOr(0), Column.getValueOr(0)}}); - - return Error::success(); -} - -Error YAMLRemarkParser::parseYAMLElement(yaml::Document &Remark) { - // Parsing a new remark, clear the previous one. - LastRemark = None; - State = ParseState(TmpArgs); - - auto *Root = dyn_cast(Remark.getRoot()); - if (!Root) - return make_error("document root is not of mapping type.", - *Remark.getRoot()); - - State.Type = Root->getRawTag(); - - for (yaml::KeyValueNode &RemarkField : *Root) { - StringRef KeyName; - if (Error E = parseKey(KeyName, RemarkField)) - return E; - - if (KeyName == "Pass") { - if (Error E = parseValue(State.Pass, RemarkField)) - return E; - } else if (KeyName == "Name") { - if (Error E = parseValue(State.Name, RemarkField)) - return E; - } else if (KeyName == "Function") { - if (Error E = parseValue(State.Function, RemarkField)) - return E; - } else if (KeyName == "Hotness") { - if (Error E = parseValue(State.Hotness, RemarkField)) - return E; - } else if (KeyName == "DebugLoc") { - if (Error E = - parseDebugLoc(State.File, State.Line, State.Column, RemarkField)) - return E; - } else if (KeyName == "Args") { - auto *Args = dyn_cast(RemarkField.getValue()); - if (!Args) - return make_error("wrong value type for key.", RemarkField); - - for (yaml::Node &Arg : *Args) - if (Error E = parseArg(*State.Args, Arg)) - return E; - } else { - return make_error("unknown key.", RemarkField); - } - } - - // If the YAML parsing failed, don't even continue parsing. We might - // encounter malformed YAML. - if (Stream.failed()) - return make_error("YAML parsing failed.", *Remark.getRoot()); - - // Check if any of the mandatory fields are missing. - if (State.Type.empty() || State.Pass.empty() || State.Name.empty() || - State.Function.empty()) - return make_error("Type, Pass, Name or Function missing.", - *Remark.getRoot()); - - LastRemark = LLVMRemarkEntry{ - toRemarkStr(State.Type), - toRemarkStr(State.Pass), - toRemarkStr(State.Name), - toRemarkStr(State.Function), - LLVMRemarkDebugLoc{toRemarkStr(State.File.getValueOr(StringRef())), - State.Line.getValueOr(0), State.Column.getValueOr(0)}, - State.Hotness.getValueOr(0), - static_cast(State.Args->size()), - State.Args->data()}; - - return Error::success(); -} -} // namespace - -// Create wrappers for C Binding types (see CBindingWrapping.h). -DEFINE_SIMPLE_CONVERSION_FUNCTIONS(YAMLRemarkParser, LLVMRemarkParserRef) - -extern "C" LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf, - uint64_t Size) { - return wrap( - new YAMLRemarkParser(StringRef(static_cast(Buf), Size))); -} - -extern "C" LLVMRemarkEntry * -LLVMRemarkParserGetNext(LLVMRemarkParserRef Parser) { - YAMLRemarkParser &TheParser = *unwrap(Parser); +static Expected getNextYAML(YAMLParserImpl &Impl) { + YAMLRemarkParser &YAMLParser = Impl.YAMLParser; // Check for EOF. - if (TheParser.HadAnyErrors || TheParser.DI == TheParser.Stream.end()) + if (Impl.YAMLIt == Impl.YAMLParser.Stream.end()) return nullptr; + auto CurrentIt = Impl.YAMLIt; + // Try to parse an entry. - if (Error E = TheParser.parseYAMLElement(*TheParser.DI)) { - handleAllErrors(std::move(E), [&](const ParseError &PE) { - TheParser.Stream.printError(&PE.getNode(), - Twine(PE.getMessage()) + Twine('\n')); - TheParser.HadAnyErrors = true; - }); - return nullptr; + if (Error E = YAMLParser.parseYAMLElement(*CurrentIt)) { + // Set the iterator to the end, in case the user calls getNext again. + Impl.YAMLIt = Impl.YAMLParser.Stream.end(); + return std::move(E); } // Move on. - ++TheParser.DI; + ++Impl.YAMLIt; // Return the just-parsed remark. - if (Optional &Entry = TheParser.LastRemark) - return &*Entry; - return nullptr; + if (const Optional &State = YAMLParser.State) + return &State->TheRemark; + else + return createStringError(std::make_error_code(std::errc::invalid_argument), + "unexpected error while parsing."); +} + +Expected Parser::getNext() const { + if (auto *Impl = dyn_cast(this->Impl.get())) + return getNextYAML(*Impl); + llvm_unreachable("Get next called with an unknown parsing implementation."); +} + +// Create wrappers for C Binding types (see CBindingWrapping.h). +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(remarks::Parser, LLVMRemarkParserRef) + +extern "C" LLVMRemarkParserRef LLVMRemarkParserCreateYAML(const void *Buf, + uint64_t Size) { + return wrap( + new remarks::Parser(StringRef(static_cast(Buf), Size))); +} + +static void handleYAMLError(remarks::YAMLParserImpl &Impl, Error E) { + handleAllErrors( + std::move(E), + [&](const YAMLParseError &PE) { + Impl.YAMLParser.Stream.printError(&PE.getNode(), + Twine(PE.getMessage()) + Twine('\n')); + }, + [&](const ErrorInfoBase &EIB) { EIB.log(Impl.YAMLParser.ErrorStream); }); + Impl.HasErrors = true; +} + +extern "C" LLVMRemarkEntryRef +LLVMRemarkParserGetNext(LLVMRemarkParserRef Parser) { + remarks::Parser &TheParser = *unwrap(Parser); + + Expected RemarkOrErr = TheParser.getNext(); + if (!RemarkOrErr) { + // Error during parsing. + if (auto *Impl = dyn_cast(TheParser.Impl.get())) + handleYAMLError(*Impl, RemarkOrErr.takeError()); + else + llvm_unreachable("unkown parser implementation."); + return nullptr; + } + + if (*RemarkOrErr == nullptr) + return nullptr; + // Valid remark. + return wrap(*RemarkOrErr); } extern "C" LLVMBool LLVMRemarkParserHasError(LLVMRemarkParserRef Parser) { - return unwrap(Parser)->HadAnyErrors; + if (auto *Impl = + dyn_cast(unwrap(Parser)->Impl.get())) + return Impl->HasErrors; + llvm_unreachable("unkown parser implementation."); } extern "C" const char * LLVMRemarkParserGetErrorMessage(LLVMRemarkParserRef Parser) { - return unwrap(Parser)->ErrorStream.str().c_str(); + if (auto *Impl = + dyn_cast(unwrap(Parser)->Impl.get())) + return Impl->YAMLParser.ErrorStream.str().c_str(); + llvm_unreachable("unkown parser implementation."); } extern "C" void LLVMRemarkParserDispose(LLVMRemarkParserRef Parser) { diff --git a/lib/Remarks/RemarkParserImpl.h b/lib/Remarks/RemarkParserImpl.h new file mode 100644 index 00000000000..7e7181e3a3e --- /dev/null +++ b/lib/Remarks/RemarkParserImpl.h @@ -0,0 +1,29 @@ +//===-- RemarkParserImpl.h - Implementation details -------------*- 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 provides implementation details for the remark parser. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_REMARKS_REMARK_PARSER_IMPL_H +#define LLVM_REMARKS_REMARK_PARSER_IMPL_H + +namespace llvm { +namespace remarks { +/// This is used as a base for any parser implementation. +struct ParserImpl { + enum class Kind { YAML }; + + // The parser kind. This is used as a tag to safely cast between + // implementations. + enum Kind ParserKind; +}; +} // end namespace remarks +} // end namespace llvm + +#endif /* LLVM_REMARKS_REMARK_PARSER_IMPL_H */ diff --git a/lib/Remarks/YAMLRemarkParser.cpp b/lib/Remarks/YAMLRemarkParser.cpp new file mode 100644 index 00000000000..76b6bbcb528 --- /dev/null +++ b/lib/Remarks/YAMLRemarkParser.cpp @@ -0,0 +1,262 @@ +//===- YAMLRemarkParser.cpp -----------------------------------------------===// +// +// 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 provides utility methods used by clients that want to use the +// parser for remark diagnostics in LLVM. +// +//===----------------------------------------------------------------------===// + +#include "YAMLRemarkParser.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Remarks/RemarkParser.h" + +using namespace llvm; +using namespace llvm::remarks; + +char YAMLParseError::ID = 0; + +Error YAMLRemarkParser::parseKey(StringRef &Result, yaml::KeyValueNode &Node) { + if (auto *Key = dyn_cast(Node.getKey())) { + Result = Key->getRawValue(); + return Error::success(); + } + + return make_error("key is not a string.", Node); +} + +template +Error YAMLRemarkParser::parseStr(T &Result, yaml::KeyValueNode &Node) { + auto *Value = dyn_cast(Node.getValue()); + if (!Value) + return make_error("expected a value of scalar type.", Node); + StringRef Tmp = Value->getRawValue(); + + if (Tmp.front() == '\'') + Tmp = Tmp.drop_front(); + + if (Tmp.back() == '\'') + Tmp = Tmp.drop_back(); + + Result = Tmp; + + return Error::success(); +} + +template +Error YAMLRemarkParser::parseUnsigned(T &Result, yaml::KeyValueNode &Node) { + SmallVector Tmp; + auto *Value = dyn_cast(Node.getValue()); + if (!Value) + return make_error("expected a value of scalar type.", Node); + unsigned UnsignedValue = 0; + if (Value->getValue(Tmp).getAsInteger(10, UnsignedValue)) + return make_error("expected a value of integer type.", + *Value); + Result = UnsignedValue; + return Error::success(); +} + +Error YAMLRemarkParser::parseType(Type &Result, yaml::MappingNode &Node) { + auto Type = StringSwitch(Node.getRawTag()) + .Case("!Passed", Type::Passed) + .Case("!Missed", Type::Missed) + .Case("!Analysis", Type::Analysis) + .Case("!AnalysisFPCommute", Type::AnalysisFPCommute) + .Case("!AnalysisAliasing", Type::AnalysisAliasing) + .Case("!Failure", Type::Failure) + .Default(Type::Unknown); + if (Type == Type::Unknown) + return make_error("expected a remark tag.", Node); + Result = Type; + return Error::success(); +} + +Error YAMLRemarkParser::parseDebugLoc(Optional &Result, + yaml::KeyValueNode &Node) { + auto *DebugLoc = dyn_cast(Node.getValue()); + if (!DebugLoc) + return make_error("expected a value of mapping type.", + Node); + + Optional File; + Optional Line; + Optional Column; + + for (yaml::KeyValueNode &DLNode : *DebugLoc) { + StringRef KeyName; + if (Error E = parseKey(KeyName, DLNode)) + return E; + if (KeyName == "File") { + if (Error E = parseStr(File, DLNode)) + return E; + } else if (KeyName == "Column") { + if (Error E = parseUnsigned(Column, DLNode)) + return E; + } else if (KeyName == "Line") { + if (Error E = parseUnsigned(Line, DLNode)) + return E; + } else { + return make_error("unknown entry in DebugLoc map.", + DLNode); + } + } + + // If any of the debug loc fields is missing, return an error. + if (!File || !Line || !Column) + return make_error("DebugLoc node incomplete.", Node); + + Result = RemarkLocation{*File, *Line, *Column}; + + return Error::success(); +} + +Error YAMLRemarkParser::parseRemarkField(yaml::KeyValueNode &RemarkField) { + + StringRef KeyName; + if (Error E = parseKey(KeyName, RemarkField)) + return E; + + if (KeyName == "Pass") { + if (Error E = parseStr(State->TheRemark.PassName, RemarkField)) + return E; + } else if (KeyName == "Name") { + if (Error E = parseStr(State->TheRemark.RemarkName, RemarkField)) + return E; + } else if (KeyName == "Function") { + if (Error E = parseStr(State->TheRemark.FunctionName, RemarkField)) + return E; + } else if (KeyName == "Hotness") { + State->TheRemark.Hotness = 0; + if (Error E = parseUnsigned(*State->TheRemark.Hotness, RemarkField)) + return E; + } else if (KeyName == "DebugLoc") { + if (Error E = parseDebugLoc(State->TheRemark.Loc, RemarkField)) + return E; + } else if (KeyName == "Args") { + auto *Args = dyn_cast(RemarkField.getValue()); + if (!Args) + return make_error("wrong value type for key.", + RemarkField); + + for (yaml::Node &Arg : *Args) + if (Error E = parseArg(State->Args, Arg)) + return E; + + State->TheRemark.Args = State->Args; + } else { + return make_error("unknown key.", RemarkField); + } + + return Error::success(); +} + +Error YAMLRemarkParser::parseArg(SmallVectorImpl &Args, + yaml::Node &Node) { + auto *ArgMap = dyn_cast(&Node); + if (!ArgMap) + return make_error("expected a value of mapping type.", + Node); + + StringRef KeyStr; + StringRef ValueStr; + Optional Loc; + + for (yaml::KeyValueNode &ArgEntry : *ArgMap) + if (Error E = parseArgEntry(ArgEntry, KeyStr, ValueStr, Loc)) + return E; + + if (KeyStr.empty()) + return make_error("argument key is missing.", *ArgMap); + if (ValueStr.empty()) + return make_error("argument value is missing.", *ArgMap); + + Args.push_back(Argument{KeyStr, ValueStr, Loc}); + + return Error::success(); +} + +Error YAMLRemarkParser::parseArgEntry(yaml::KeyValueNode &ArgEntry, + StringRef &KeyStr, StringRef &ValueStr, + Optional &Loc) { + StringRef KeyName; + if (Error E = parseKey(KeyName, ArgEntry)) + return E; + + // Try to parse debug locs. + if (KeyName == "DebugLoc") { + // Can't have multiple DebugLoc entries per argument. + if (Loc) + return make_error( + "only one DebugLoc entry is allowed per argument.", ArgEntry); + + if (Error E = parseDebugLoc(Loc, ArgEntry)) + return E; + return Error::success(); + } + + // If we already have a string, error out. + if (!ValueStr.empty()) + return make_error( + "only one string entry is allowed per argument.", ArgEntry); + + // Try to parse a string. + if (Error E = parseStr(ValueStr, ArgEntry)) + return E; + + // Keep the key from the string. + KeyStr = KeyName; + return Error::success(); +} + +Error YAMLRemarkParser::parseYAMLElement(yaml::Document &Remark) { + // Parsing a new remark, clear the previous one by re-constructing the state + // in-place in the Optional. + State.emplace(TmpArgs); + + yaml::Node *YAMLRoot = Remark.getRoot(); + if (!YAMLRoot) + return createStringError(std::make_error_code(std::errc::invalid_argument), + "not a valid YAML file."); + + auto *Root = dyn_cast(YAMLRoot); + if (!Root) + return make_error("document root is not of mapping type.", + *YAMLRoot); + + if (Error E = parseType(State->TheRemark.RemarkType, *Root)) + return E; + + for (yaml::KeyValueNode &RemarkField : *Root) + if (Error E = parseRemarkField(RemarkField)) + return E; + + // If the YAML parsing failed, don't even continue parsing. We might + // encounter malformed YAML. + if (Stream.failed()) + return make_error("YAML parsing failed.", + *Remark.getRoot()); + + // Check if any of the mandatory fields are missing. + if (State->TheRemark.RemarkType == Type::Unknown || + State->TheRemark.PassName.empty() || + State->TheRemark.RemarkName.empty() || + State->TheRemark.FunctionName.empty()) + return make_error("Type, Pass, Name or Function missing.", + *Remark.getRoot()); + + return Error::success(); +} + +/// Handle a diagnostic from the YAML stream. Records the error in the +/// YAMLRemarkParser class. +void YAMLRemarkParser::HandleDiagnostic(const SMDiagnostic &Diag, void *Ctx) { + assert(Ctx && "Expected non-null Ctx in diagnostic handler."); + auto *Parser = static_cast(Ctx); + Diag.print(/*ProgName=*/nullptr, Parser->ErrorStream, /*ShowColors*/ false, + /*ShowKindLabels*/ true); +} diff --git a/lib/Remarks/YAMLRemarkParser.h b/lib/Remarks/YAMLRemarkParser.h new file mode 100644 index 00000000000..5095a4be730 --- /dev/null +++ b/lib/Remarks/YAMLRemarkParser.h @@ -0,0 +1,136 @@ +//===-- YAMLRemarkParser.h - Parser for YAML remarks ------------*- 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 provides the impementation of the YAML remark parser. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_REMARKS_YAML_REMARK_PARSER_H +#define LLVM_REMARKS_YAML_REMARK_PARSER_H + +#include "RemarkParserImpl.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Remarks/Remark.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace llvm { +namespace remarks { +/// Parses and holds the state of the latest parsed remark. +struct YAMLRemarkParser { + /// Source manager for better error messages. + SourceMgr SM; + /// Stream for yaml parsing. + yaml::Stream Stream; + /// Storage for the error stream. + std::string ErrorString; + /// The error stream. + raw_string_ostream ErrorStream; + /// Temporary parsing buffer for the arguments. + SmallVector TmpArgs; + + /// The state used by the parser to parse a remark entry. Invalidated with + /// every call to `parseYAMLElement`. + struct ParseState { + /// Temporary parsing buffer for the arguments. + /// The parser itself is owning this buffer in order to reduce the number of + /// allocations. + SmallVectorImpl &Args; + Remark TheRemark; + + ParseState(SmallVectorImpl &Args) : Args(Args) {} + /// Use Args only as a **temporary** buffer. + ~ParseState() { Args.clear(); } + }; + + /// The current state of the parser. If the parsing didn't start yet, it will + /// not be containing any value. + Optional State; + + YAMLRemarkParser(StringRef Buf) + : SM(), Stream(Buf, SM), ErrorString(), ErrorStream(ErrorString), + TmpArgs() { + SM.setDiagHandler(YAMLRemarkParser::HandleDiagnostic, this); + } + + /// Parse a YAML element. + Error parseYAMLElement(yaml::Document &Remark); + +private: + /// Parse one key to a string. + /// otherwise. + Error parseKey(StringRef &Result, yaml::KeyValueNode &Node); + /// Parse one value to a string. + template Error parseStr(T &Result, yaml::KeyValueNode &Node); + /// Parse one value to an unsigned. + template + Error parseUnsigned(T &Result, yaml::KeyValueNode &Node); + /// Parse the type of a remark to an enum type. + Error parseType(Type &Result, yaml::MappingNode &Node); + /// Parse a debug location. + Error parseDebugLoc(Optional &Result, + yaml::KeyValueNode &Node); + /// Parse a remark field and update the parsing state. + Error parseRemarkField(yaml::KeyValueNode &RemarkField); + /// Parse an argument. + Error parseArg(SmallVectorImpl &TmpArgs, yaml::Node &Node); + /// Parse an entry from the contents of an argument. + Error parseArgEntry(yaml::KeyValueNode &ArgEntry, StringRef &KeyStr, + StringRef &ValueStr, Optional &Loc); + + /// Handle a diagnostic from the YAML stream. Records the error in the + /// YAMLRemarkParser class. + static void HandleDiagnostic(const SMDiagnostic &Diag, void *Ctx); +}; + +class YAMLParseError : public ErrorInfo { +public: + static char ID; + + YAMLParseError(StringRef Message, yaml::Node &Node) + : Message(Message), Node(Node) {} + + void log(raw_ostream &OS) const override { OS << Message; } + std::error_code convertToErrorCode() const override { + return inconvertibleErrorCode(); + } + + StringRef getMessage() const { return Message; } + yaml::Node &getNode() const { return Node; } + +private: + StringRef Message; // No need to hold a full copy of the buffer. + yaml::Node &Node; +}; + +/// Regular YAML to Remark parser. +struct YAMLParserImpl : public ParserImpl { + /// The object parsing the YAML. + YAMLRemarkParser YAMLParser; + /// Iterator in the YAML stream. + yaml::document_iterator YAMLIt; + /// Set to `true` if we had any errors during parsing. + bool HasErrors = false; + + YAMLParserImpl(StringRef Buf) + : ParserImpl{ParserImpl::Kind::YAML}, YAMLParser(Buf), + YAMLIt(YAMLParser.Stream.begin()), HasErrors(false) {} + + static bool classof(const ParserImpl *PI) { + return PI->ParserKind == ParserImpl::Kind::YAML; + } +}; +} // end namespace remarks +} // end namespace llvm + +#endif /* LLVM_REMARKS_YAML_REMARK_PARSER_H */ diff --git a/tools/llvm-opt-report/OptReport.cpp b/tools/llvm-opt-report/OptReport.cpp index 43718eaedc0..3ea71fc47ef 100644 --- a/tools/llvm-opt-report/OptReport.cpp +++ b/tools/llvm-opt-report/OptReport.cpp @@ -15,6 +15,7 @@ #include "llvm-c/Remarks.h" #include "llvm/Demangle/Demangle.h" +#include "llvm/Remarks/RemarkParser.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorOr.h" @@ -151,40 +152,44 @@ static bool readLocationInfo(LocationInfoTy &LocationInfo) { return false; } - StringRef Buffer = (*Buf)->getBuffer(); - LLVMRemarkParserRef Parser = - LLVMRemarkParserCreate(Buffer.data(), Buffer.size()); + remarks::Parser Parser((*Buf)->getBuffer()); - LLVMRemarkEntry *Remark = nullptr; - while ((Remark = LLVMRemarkParserGetNext(Parser))) { - bool Transformed = - StringRef(Remark->RemarkType.Str, Remark->RemarkType.Len) == "!Passed"; - StringRef Pass(Remark->PassName.Str, Remark->PassName.Len); - StringRef File(Remark->DebugLoc.SourceFile.Str, - Remark->DebugLoc.SourceFile.Len); - StringRef Function(Remark->FunctionName.Str, Remark->FunctionName.Len); - uint32_t Line = Remark->DebugLoc.SourceLineNumber; - uint32_t Column = Remark->DebugLoc.SourceColumnNumber; - ArrayRef Args(Remark->Args, Remark->NumArgs); + while (true) { + Expected RemarkOrErr = Parser.getNext(); + if (!RemarkOrErr) { + handleAllErrors(RemarkOrErr.takeError(), [&](const ErrorInfoBase &PE) { + PE.log(WithColor::error()); + }); + return false; + } + if (!*RemarkOrErr) // End of file. + break; + + const remarks::Remark &Remark = **RemarkOrErr; + + bool Transformed = Remark.RemarkType == remarks::Type::Passed; int VectorizationFactor = 1; int InterleaveCount = 1; int UnrollCount = 1; - for (const LLVMRemarkArg &Arg : Args) { - StringRef ArgKeyName(Arg.Key.Str, Arg.Key.Len); - StringRef ArgValue(Arg.Value.Str, Arg.Value.Len); - if (ArgKeyName == "VectorizationFactor") - ArgValue.getAsInteger(10, VectorizationFactor); - else if (ArgKeyName == "InterleaveCount") - ArgValue.getAsInteger(10, InterleaveCount); - else if (ArgKeyName == "UnrollCount") - ArgValue.getAsInteger(10, UnrollCount); + for (const remarks::Argument &Arg : Remark.Args) { + if (Arg.Key == "VectorizationFactor") + Arg.Val.getAsInteger(10, VectorizationFactor); + else if (Arg.Key == "InterleaveCount") + Arg.Val.getAsInteger(10, InterleaveCount); + else if (Arg.Key == "UnrollCount") + Arg.Val.getAsInteger(10, UnrollCount); } - if (Line < 1 || File.empty()) + const Optional &Loc = Remark.Loc; + if (!Loc) continue; + StringRef File = Loc->SourceFilePath; + unsigned Line = Loc->SourceLine; + unsigned Column = Loc->SourceColumn; + // We track information on both actual and potential transformations. This // way, if there are multiple possible things on a line that are, or could // have been transformed, we can indicate that explicitly in the output. @@ -194,27 +199,22 @@ static bool readLocationInfo(LocationInfoTy &LocationInfo) { LLII.Transformed = true; }; - if (Pass == "inline") { - auto &LI = LocationInfo[File][Line][Function][Column]; + if (Remark.PassName == "inline") { + auto &LI = LocationInfo[File][Line][Remark.FunctionName][Column]; UpdateLLII(LI.Inlined); - } else if (Pass == "loop-unroll") { - auto &LI = LocationInfo[File][Line][Function][Column]; + } else if (Remark.PassName == "loop-unroll") { + auto &LI = LocationInfo[File][Line][Remark.FunctionName][Column]; LI.UnrollCount = UnrollCount; UpdateLLII(LI.Unrolled); - } else if (Pass == "loop-vectorize") { - auto &LI = LocationInfo[File][Line][Function][Column]; + } else if (Remark.PassName == "loop-vectorize") { + auto &LI = LocationInfo[File][Line][Remark.FunctionName][Column]; LI.VectorizationFactor = VectorizationFactor; LI.InterleaveCount = InterleaveCount; UpdateLLII(LI.Vectorized); } } - bool HasError = LLVMRemarkParserHasError(Parser); - if (HasError) - WithColor::error() << LLVMRemarkParserGetErrorMessage(Parser) << "\n"; - - LLVMRemarkParserDispose(Parser); - return !HasError; + return true; } static bool writeReport(LocationInfoTy &LocationInfo) { diff --git a/tools/remarks-shlib/Remarks.exports b/tools/remarks-shlib/Remarks.exports index c54892ea27c..7260f9a543d 100644 --- a/tools/remarks-shlib/Remarks.exports +++ b/tools/remarks-shlib/Remarks.exports @@ -1,4 +1,21 @@ -LLVMRemarkParserCreate +LLVMRemarkStringGetData +LLVMRemarkStringGetLen +LLVMRemarkDebugLocGetSourceFilePath +LLVMRemarkDebugLocGetSourceLine +LLVMRemarkDebugLocGetSourceColumn +LLVMRemarkArgGetKey +LLVMRemarkArgGetValue +LLVMRemarkArgGetDebugLoc +LLVMRemarkEntryGetType +LLVMRemarkEntryGetPassName +LLVMRemarkEntryGetRemarkName +LLVMRemarkEntryGetFunctionName +LLVMRemarkEntryGetDebugLoc +LLVMRemarkEntryGetHotness +LLVMRemarkEntryGetNumArgs +LLVMRemarkEntryGetFirstArg +LLVMRemarkEntryGetNextArg +LLVMRemarkParserCreateYAML LLVMRemarkParserGetNext LLVMRemarkParserHasError LLVMRemarkParserGetErrorMessage diff --git a/unittests/Remarks/CMakeLists.txt b/unittests/Remarks/CMakeLists.txt index 7d80cf2348d..b7d22b694ef 100644 --- a/unittests/Remarks/CMakeLists.txt +++ b/unittests/Remarks/CMakeLists.txt @@ -4,5 +4,5 @@ set(LLVM_LINK_COMPONENTS ) add_llvm_unittest(RemarksTests - RemarksParsingTest.cpp + YAMLRemarksParsingTest.cpp ) diff --git a/unittests/Remarks/RemarksParsingTest.cpp b/unittests/Remarks/RemarksParsingTest.cpp deleted file mode 100644 index a167b8318a4..00000000000 --- a/unittests/Remarks/RemarksParsingTest.cpp +++ /dev/null @@ -1,436 +0,0 @@ -//===- unittest/Support/RemarksParsingTest.cpp - OptTable tests --------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -#include "llvm-c/Remarks.h" -#include "gtest/gtest.h" - -using namespace llvm; - -template bool tryParse(const char (&Buf)[N]) { - LLVMRemarkParserRef Parser = LLVMRemarkParserCreate(Buf, N - 1); - LLVMRemarkEntry *Remark = nullptr; - while (LLVMRemarkEntry *NewRemark = LLVMRemarkParserGetNext(Parser)) { - EXPECT_TRUE(Remark == nullptr); // Only one remark per test. - Remark = NewRemark; - } - EXPECT_TRUE(Remark != nullptr); // We need *exactly* one remark per test. - bool HasError = LLVMRemarkParserHasError(Parser); - LLVMRemarkParserDispose(Parser); - return !HasError; -} - -template -bool parseExpectError(const char (&Buf)[N], const char *Error) { - LLVMRemarkParserRef Parser = LLVMRemarkParserCreate(Buf, N - 1); - LLVMRemarkEntry *Remark = nullptr; - while (LLVMRemarkEntry *NewRemark = LLVMRemarkParserGetNext(Parser)) { - EXPECT_FALSE(NewRemark); - } - EXPECT_TRUE(Remark == nullptr); // We are parsing only one malformed remark. - EXPECT_TRUE(LLVMRemarkParserHasError(Parser)); - bool MatchesError = - StringRef(LLVMRemarkParserGetErrorMessage(Parser)).contains(Error); - LLVMRemarkParserDispose(Parser); - - return MatchesError; -} - -TEST(Remarks, RemarksParsingEmpty) { - StringRef Buf = "\n" - "\n"; - LLVMRemarkParserRef Parser = LLVMRemarkParserCreate(Buf.data(), Buf.size()); - LLVMRemarkEntry *NewRemark = LLVMRemarkParserGetNext(Parser); - EXPECT_TRUE(NewRemark == nullptr); // No remark expected. - EXPECT_TRUE(LLVMRemarkParserHasError(Parser)); - EXPECT_TRUE(StringRef(LLVMRemarkParserGetErrorMessage(Parser)) - .contains("document root is not of mapping type.")); - LLVMRemarkParserDispose(Parser); -} - -TEST(Remarks, RemarksParsingGood) { - EXPECT_TRUE(tryParse("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" - "Function: foo\n" - "Args:\n" - " - Callee: bar\n" - " - String: ' will not be inlined into '\n" - " - Caller: foo\n" - " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" - " - String: ' because its definition is unavailable'\n" - "")); - - // No debug loc should also pass. - EXPECT_TRUE(tryParse("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "Args:\n" - " - Callee: bar\n" - " - String: ' will not be inlined into '\n" - " - Caller: foo\n" - " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" - " - String: ' because its definition is unavailable'\n" - "")); - - // No args is also ok. - EXPECT_TRUE(tryParse("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" - "Function: foo\n" - "")); - - // Different order. - EXPECT_TRUE(tryParse("\n" - "--- !Missed\n" - "DebugLoc: { Line: 3, Column: 12, File: file.c }\n" - "Function: foo\n" - "Name: NoDefinition\n" - "Args:\n" - " - Callee: bar\n" - " - String: ' will not be inlined into '\n" - " - Caller: foo\n" - " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" - " - String: ' because its definition is unavailable'\n" - "Pass: inline\n" - "")); -} - -// Mandatory common part of a remark. -#define COMMON_REMARK "\nPass: inline\nName: NoDefinition\nFunction: foo\n\n" -// Test all the types. -TEST(Remarks, RemarksParsingTypes) { - // Type: Passed - EXPECT_TRUE(tryParse("--- !Passed" COMMON_REMARK)); - // Type: Missed - EXPECT_TRUE(tryParse("--- !Missed" COMMON_REMARK)); - // Type: Analysis - EXPECT_TRUE(tryParse("--- !Analysis" COMMON_REMARK)); - // Type: AnalysisFPCompute - EXPECT_TRUE(tryParse("--- !AnalysisFPCompute" COMMON_REMARK)); - // Type: AnalysisAliasing - EXPECT_TRUE(tryParse("--- !AnalysisAliasing" COMMON_REMARK)); - // Type: Failure - EXPECT_TRUE(tryParse("--- !Failure" COMMON_REMARK)); -} -#undef COMMON_REMARK - -TEST(Remarks, RemarksParsingMissingFields) { - // No type. - EXPECT_TRUE(parseExpectError("\n" - "---\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "", - "error: Type, Pass, Name or Function missing.")); - // No pass. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Name: NoDefinition\n" - "Function: foo\n" - "", - "error: Type, Pass, Name or Function missing.")); - // No name. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Function: foo\n" - "", - "error: Type, Pass, Name or Function missing.")); - // No function. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "", - "error: Type, Pass, Name or Function missing.")); - // Debug loc but no file. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { Line: 3, Column: 12 }\n" - "", - "DebugLoc node incomplete.")); - // Debug loc but no line. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Column: 12 }\n" - "", - "DebugLoc node incomplete.")); - // Debug loc but no column. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Line: 3 }\n" - "", - "DebugLoc node incomplete.")); -} - -TEST(Remarks, RemarksParsingWrongTypes) { - // Wrong debug loc type. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: foo\n" - "", - "expected a value of mapping type.")); - // Wrong line type. - EXPECT_TRUE( - parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Line: b, Column: 12 }\n" - "", - "expected a value of integer type.")); - // Wrong column type. - EXPECT_TRUE( - parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Line: 3, Column: c }\n" - "", - "expected a value of integer type.")); - // Wrong args type. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "Args: foo\n" - "", - "wrong value type for key.")); - // Wrong key type. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "{ A: a }: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "", - "key is not a string.")); - // Debug loc with unknown entry. - EXPECT_TRUE( - parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Column: 12, Unknown: 12 }\n" - "", - "unknown entry in DebugLoc map.")); - // Unknown entry. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Unknown: inline\n" - "", - "unknown key.")); - // Not a scalar. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: { File: a, Line: 1, Column: 2 }\n" - "Name: NoDefinition\n" - "Function: foo\n" - "", - "expected a value of scalar type.")); - // Not a string file in debug loc. - EXPECT_TRUE( - parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: { a: b }, Column: 12, Line: 12 }\n" - "", - "expected a value of scalar type.")); - // Not a integer column in debug loc. - EXPECT_TRUE(parseExpectError( - "\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Column: { a: b }, Line: 12 }\n" - "", - "expected a value of scalar type.")); - // Not a integer line in debug loc. - EXPECT_TRUE(parseExpectError( - "\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Column: 12, Line: { a: b } }\n" - "", - "expected a value of scalar type.")); - // Not a mapping type value for args. - EXPECT_TRUE(parseExpectError( - "\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "DebugLoc: { File: file.c, Column: 12, Line: { a: b } }\n" - "", - "expected a value of scalar type.")); -} - -TEST(Remarks, RemarksParsingWrongArgs) { - // Multiple debug locs per arg. - EXPECT_TRUE( - parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "Args:\n" - " - Str: string\n" - " DebugLoc: { File: a, Line: 1, Column: 2 }\n" - " DebugLoc: { File: a, Line: 1, Column: 2 }\n" - "", - "only one DebugLoc entry is allowed per argument.")); - // Multiple strings per arg. - EXPECT_TRUE( - parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "Args:\n" - " - Str: string\n" - " Str2: string\n" - " DebugLoc: { File: a, Line: 1, Column: 2 }\n" - "", - "only one string entry is allowed per argument.")); - // No arg value. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "Args:\n" - " - Callee: ''\n" - " - DebugLoc: { File: a, Line: 1, Column: 2 }\n" - "", - "argument value is missing.")); - // No arg value. - EXPECT_TRUE(parseExpectError("\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "Function: foo\n" - "Args:\n" - " - DebugLoc: { File: a, Line: 1, Column: 2 }\n" - "", - "argument key is missing.")); -} - -TEST(Remarks, RemarksGoodStruct) { - StringRef Buf = "\n" - "--- !Missed\n" - "Pass: inline\n" - "Name: NoDefinition\n" - "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" - "Function: foo\n" - "Args:\n" - " - Callee: bar\n" - " - String: ' will not be inlined into '\n" - " - Caller: foo\n" - " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" - " - String: ' because its definition is unavailable'\n" - "\n"; - - LLVMRemarkParserRef Parser = LLVMRemarkParserCreate(Buf.data(), Buf.size()); - LLVMRemarkEntry *Remark = LLVMRemarkParserGetNext(Parser); - EXPECT_FALSE(Remark == nullptr); - EXPECT_EQ(StringRef(Remark->RemarkType.Str, 7), "!Missed"); - EXPECT_EQ(Remark->RemarkType.Len, 7U); - EXPECT_EQ(StringRef(Remark->PassName.Str, 6), "inline"); - EXPECT_EQ(Remark->PassName.Len, 6U); - EXPECT_EQ(StringRef(Remark->RemarkName.Str, 12), "NoDefinition"); - EXPECT_EQ(Remark->RemarkName.Len, 12U); - EXPECT_EQ(StringRef(Remark->FunctionName.Str, 3), "foo"); - EXPECT_EQ(Remark->FunctionName.Len, 3U); - EXPECT_EQ(StringRef(Remark->DebugLoc.SourceFile.Str, 6), "file.c"); - EXPECT_EQ(Remark->DebugLoc.SourceFile.Len, 6U); - EXPECT_EQ(Remark->DebugLoc.SourceLineNumber, 3U); - EXPECT_EQ(Remark->DebugLoc.SourceColumnNumber, 12U); - EXPECT_EQ(Remark->Hotness, 0U); - EXPECT_EQ(Remark->NumArgs, 4U); - // Arg 0 - { - LLVMRemarkArg &Arg = Remark->Args[0]; - EXPECT_EQ(StringRef(Arg.Key.Str, 6), "Callee"); - EXPECT_EQ(Arg.Key.Len, 6U); - EXPECT_EQ(StringRef(Arg.Value.Str, 3), "bar"); - EXPECT_EQ(Arg.Value.Len, 3U); - EXPECT_EQ(StringRef(Arg.DebugLoc.SourceFile.Str, 0), ""); - EXPECT_EQ(Arg.DebugLoc.SourceFile.Len, 0U); - EXPECT_EQ(Arg.DebugLoc.SourceLineNumber, 0U); - EXPECT_EQ(Arg.DebugLoc.SourceColumnNumber, 0U); - } - // Arg 1 - { - LLVMRemarkArg &Arg = Remark->Args[1]; - EXPECT_EQ(StringRef(Arg.Key.Str, 6), "String"); - EXPECT_EQ(Arg.Key.Len, 6U); - EXPECT_EQ(StringRef(Arg.Value.Str, 26), " will not be inlined into "); - EXPECT_EQ(Arg.Value.Len, 26U); - EXPECT_EQ(StringRef(Arg.DebugLoc.SourceFile.Str, 0), ""); - EXPECT_EQ(Arg.DebugLoc.SourceFile.Len, 0U); - EXPECT_EQ(Arg.DebugLoc.SourceLineNumber, 0U); - EXPECT_EQ(Arg.DebugLoc.SourceColumnNumber, 0U); - } - // Arg 2 - { - LLVMRemarkArg &Arg = Remark->Args[2]; - EXPECT_EQ(StringRef(Arg.Key.Str, 6), "Caller"); - EXPECT_EQ(Arg.Key.Len, 6U); - EXPECT_EQ(StringRef(Arg.Value.Str, 3), "foo"); - EXPECT_EQ(Arg.Value.Len, 3U); - EXPECT_EQ(StringRef(Arg.DebugLoc.SourceFile.Str, 6), "file.c"); - EXPECT_EQ(Arg.DebugLoc.SourceFile.Len, 6U); - EXPECT_EQ(Arg.DebugLoc.SourceLineNumber, 2U); - EXPECT_EQ(Arg.DebugLoc.SourceColumnNumber, 0U); - } - // Arg 3 - { - LLVMRemarkArg &Arg = Remark->Args[3]; - EXPECT_EQ(StringRef(Arg.Key.Str, 6), "String"); - EXPECT_EQ(Arg.Key.Len, 6U); - EXPECT_EQ(StringRef(Arg.Value.Str, 38), - " because its definition is unavailable"); - EXPECT_EQ(Arg.Value.Len, 38U); - EXPECT_EQ(StringRef(Arg.DebugLoc.SourceFile.Str, 0), ""); - EXPECT_EQ(Arg.DebugLoc.SourceFile.Len, 0U); - EXPECT_EQ(Arg.DebugLoc.SourceLineNumber, 0U); - EXPECT_EQ(Arg.DebugLoc.SourceColumnNumber, 0U); - } - - EXPECT_EQ(LLVMRemarkParserGetNext(Parser), nullptr); - - EXPECT_FALSE(LLVMRemarkParserHasError(Parser)); - LLVMRemarkParserDispose(Parser); -} diff --git a/unittests/Remarks/YAMLRemarksParsingTest.cpp b/unittests/Remarks/YAMLRemarksParsingTest.cpp new file mode 100644 index 00000000000..36dbb0c9518 --- /dev/null +++ b/unittests/Remarks/YAMLRemarksParsingTest.cpp @@ -0,0 +1,494 @@ +//===- unittest/Support/YAMLRemarksParsingTest.cpp - OptTable tests -------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm-c/Remarks.h" +#include "llvm/Remarks/Remark.h" +#include "llvm/Remarks/RemarkParser.h" +#include "gtest/gtest.h" + +using namespace llvm; + +template void parseGood(const char (&Buf)[N]) { + remarks::Parser Parser({Buf, N - 1}); + Expected Remark = Parser.getNext(); + EXPECT_FALSE(errorToBool(Remark.takeError())); // Check for parsing errors. + EXPECT_TRUE(*Remark != nullptr); // At least one remark. + Remark = Parser.getNext(); + EXPECT_FALSE(errorToBool(Remark.takeError())); // Check for parsing errors. + EXPECT_TRUE(*Remark == nullptr); // Check that there are no more remarks. +} + +template +bool parseExpectError(const char (&Buf)[N], const char *Error) { + remarks::Parser Parser({Buf, N - 1}); + Expected Remark = Parser.getNext(); + EXPECT_FALSE(Remark); // Expect an error here. + + std::string ErrorStr; + raw_string_ostream Stream(ErrorStr); + handleAllErrors(Remark.takeError(), + [&](const ErrorInfoBase &EIB) { EIB.log(Stream); }); + return StringRef(Stream.str()).contains(Error); +} + +TEST(YAMLRemarks, ParsingEmpty) { + EXPECT_TRUE(parseExpectError("\n\n", "document root is not of mapping type.")); +} + +TEST(YAMLRemarks, ParsingNotYAML) { + EXPECT_TRUE( + parseExpectError("\x01\x02\x03\x04\x05\x06", "not a valid YAML file.")); +} + +TEST(YAMLRemarks, ParsingGood) { + parseGood("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" + "Function: foo\n" + "Args:\n" + " - Callee: bar\n" + " - String: ' will not be inlined into '\n" + " - Caller: foo\n" + " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" + " - String: ' because its definition is unavailable'\n" + ""); + + // No debug loc should also pass. + parseGood("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "Args:\n" + " - Callee: bar\n" + " - String: ' will not be inlined into '\n" + " - Caller: foo\n" + " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" + " - String: ' because its definition is unavailable'\n" + ""); + + // No args is also ok. + parseGood("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" + "Function: foo\n" + ""); + + // Different order. + parseGood("\n" + "--- !Missed\n" + "DebugLoc: { Line: 3, Column: 12, File: file.c }\n" + "Function: foo\n" + "Name: NoDefinition\n" + "Args:\n" + " - Callee: bar\n" + " - String: ' will not be inlined into '\n" + " - Caller: foo\n" + " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" + " - String: ' because its definition is unavailable'\n" + "Pass: inline\n" + ""); +} + +// Mandatory common part of a remark. +#define COMMON_REMARK "\nPass: inline\nName: NoDefinition\nFunction: foo\n\n" +// Test all the types. +TEST(YAMLRemarks, ParsingTypes) { + // Type: Passed + parseGood("--- !Passed" COMMON_REMARK); + // Type: Missed + parseGood("--- !Missed" COMMON_REMARK); + // Type: Analysis + parseGood("--- !Analysis" COMMON_REMARK); + // Type: AnalysisFPCommute + parseGood("--- !AnalysisFPCommute" COMMON_REMARK); + // Type: AnalysisAliasing + parseGood("--- !AnalysisAliasing" COMMON_REMARK); + // Type: Failure + parseGood("--- !Failure" COMMON_REMARK); +} +#undef COMMON_REMARK + +TEST(YAMLRemarks, ParsingMissingFields) { + // No type. + EXPECT_TRUE(parseExpectError("\n" + "---\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "", + "expected a remark tag.")); + // No pass. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Name: NoDefinition\n" + "Function: foo\n" + "", + "Type, Pass, Name or Function missing.")); + // No name. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Function: foo\n" + "", + "Type, Pass, Name or Function missing.")); + // No function. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "", + "Type, Pass, Name or Function missing.")); + // Debug loc but no file. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { Line: 3, Column: 12 }\n" + "", + "DebugLoc node incomplete.")); + // Debug loc but no line. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Column: 12 }\n" + "", + "DebugLoc node incomplete.")); + // Debug loc but no column. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Line: 3 }\n" + "", + "DebugLoc node incomplete.")); +} + +TEST(YAMLRemarks, ParsingWrongTypes) { + // Wrong debug loc type. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: foo\n" + "", + "expected a value of mapping type.")); + // Wrong line type. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Line: b, Column: 12 }\n" + "", + "expected a value of integer type.")); + // Wrong column type. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Line: 3, Column: c }\n" + "", + "expected a value of integer type.")); + // Wrong args type. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "Args: foo\n" + "", + "wrong value type for key.")); + // Wrong key type. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "{ A: a }: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "", + "key is not a string.")); + // Debug loc with unknown entry. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Column: 12, Unknown: 12 }\n" + "", + "unknown entry in DebugLoc map.")); + // Unknown entry. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Unknown: inline\n" + "", + "unknown key.")); + // Not a scalar. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: { File: a, Line: 1, Column: 2 }\n" + "Name: NoDefinition\n" + "Function: foo\n" + "", + "expected a value of scalar type.")); + // Not a string file in debug loc. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: { a: b }, Column: 12, Line: 12 }\n" + "", + "expected a value of scalar type.")); + // Not a integer column in debug loc. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Column: { a: b }, Line: 12 }\n" + "", + "expected a value of scalar type.")); + // Not a integer line in debug loc. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Column: 12, Line: { a: b } }\n" + "", + "expected a value of scalar type.")); + // Not a mapping type value for args. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "DebugLoc: { File: file.c, Column: 12, Line: { a: b } }\n" + "", + "expected a value of scalar type.")); +} + +TEST(YAMLRemarks, ParsingWrongArgs) { + // Multiple debug locs per arg. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "Args:\n" + " - Str: string\n" + " DebugLoc: { File: a, Line: 1, Column: 2 }\n" + " DebugLoc: { File: a, Line: 1, Column: 2 }\n" + "", + "only one DebugLoc entry is allowed per argument.")); + // Multiple strings per arg. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "Args:\n" + " - Str: string\n" + " Str2: string\n" + " DebugLoc: { File: a, Line: 1, Column: 2 }\n" + "", + "only one string entry is allowed per argument.")); + // No arg value. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "Args:\n" + " - Callee: ''\n" + " - DebugLoc: { File: a, Line: 1, Column: 2 }\n" + "", + "argument value is missing.")); + // No arg value. + EXPECT_TRUE(parseExpectError("\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "Function: foo\n" + "Args:\n" + " - DebugLoc: { File: a, Line: 1, Column: 2 }\n" + "", + "argument key is missing.")); +} + +static inline StringRef checkStr(StringRef Str, unsigned ExpectedLen) { + const char *StrData = Str.data(); + unsigned StrLen = Str.size(); + EXPECT_EQ(StrLen, ExpectedLen); + return StringRef(StrData, StrLen); +} + +TEST(YAMLRemarks, Contents) { + StringRef Buf = "\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" + "Function: foo\n" + "Hotness: 4\n" + "Args:\n" + " - Callee: bar\n" + " - String: ' will not be inlined into '\n" + " - Caller: foo\n" + " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" + " - String: ' because its definition is unavailable'\n" + "\n"; + + remarks::Parser Parser(Buf); + Expected RemarkOrErr = Parser.getNext(); + EXPECT_FALSE(errorToBool(RemarkOrErr.takeError())); + EXPECT_TRUE(*RemarkOrErr != nullptr); + + const remarks::Remark &Remark = **RemarkOrErr; + EXPECT_EQ(Remark.RemarkType, remarks::Type::Missed); + EXPECT_EQ(checkStr(Remark.PassName, 6), "inline"); + EXPECT_EQ(checkStr(Remark.RemarkName, 12), "NoDefinition"); + EXPECT_EQ(checkStr(Remark.FunctionName, 3), "foo"); + EXPECT_TRUE(Remark.Loc); + const remarks::RemarkLocation &RL = *Remark.Loc; + EXPECT_EQ(checkStr(RL.SourceFilePath, 6), "file.c"); + EXPECT_EQ(RL.SourceLine, 3U); + EXPECT_EQ(RL.SourceColumn, 12U); + EXPECT_TRUE(Remark.Hotness); + EXPECT_EQ(*Remark.Hotness, 4U); + EXPECT_EQ(Remark.Args.size(), 4U); + + unsigned ArgID = 0; + for (const remarks::Argument &Arg : Remark.Args) { + switch (ArgID) { + case 0: + EXPECT_EQ(checkStr(Arg.Key, 6), "Callee"); + EXPECT_EQ(checkStr(Arg.Val, 3), "bar"); + EXPECT_FALSE(Arg.Loc); + break; + case 1: + EXPECT_EQ(checkStr(Arg.Key, 6), "String"); + EXPECT_EQ(checkStr(Arg.Val, 26), " will not be inlined into "); + EXPECT_FALSE(Arg.Loc); + break; + case 2: { + EXPECT_EQ(checkStr(Arg.Key, 6), "Caller"); + EXPECT_EQ(checkStr(Arg.Val, 3), "foo"); + EXPECT_TRUE(Arg.Loc); + const remarks::RemarkLocation &RL = *Arg.Loc; + EXPECT_EQ(checkStr(RL.SourceFilePath, 6), "file.c"); + EXPECT_EQ(RL.SourceLine, 2U); + EXPECT_EQ(RL.SourceColumn, 0U); + break; + } + case 3: + EXPECT_EQ(checkStr(Arg.Key, 6), "String"); + EXPECT_EQ(checkStr(Arg.Val, 38), + " because its definition is unavailable"); + EXPECT_FALSE(Arg.Loc); + break; + default: + break; + } + ++ArgID; + } + + RemarkOrErr = Parser.getNext(); + EXPECT_FALSE(errorToBool(RemarkOrErr.takeError())); + EXPECT_EQ(*RemarkOrErr, nullptr); +} + +static inline StringRef checkStr(LLVMRemarkStringRef Str, + unsigned ExpectedLen) { + const char *StrData = LLVMRemarkStringGetData(Str); + unsigned StrLen = LLVMRemarkStringGetLen(Str); + EXPECT_EQ(StrLen, ExpectedLen); + return StringRef(StrData, StrLen); +} + +TEST(YAMLRemarks, ContentsCAPI) { + StringRef Buf = "\n" + "--- !Missed\n" + "Pass: inline\n" + "Name: NoDefinition\n" + "DebugLoc: { File: file.c, Line: 3, Column: 12 }\n" + "Function: foo\n" + "Args:\n" + " - Callee: bar\n" + " - String: ' will not be inlined into '\n" + " - Caller: foo\n" + " DebugLoc: { File: file.c, Line: 2, Column: 0 }\n" + " - String: ' because its definition is unavailable'\n" + "\n"; + + LLVMRemarkParserRef Parser = + LLVMRemarkParserCreateYAML(Buf.data(), Buf.size()); + LLVMRemarkEntryRef Remark = LLVMRemarkParserGetNext(Parser); + EXPECT_FALSE(Remark == nullptr); + EXPECT_EQ(LLVMRemarkEntryGetType(Remark), LLVMRemarkTypeMissed); + EXPECT_EQ(checkStr(LLVMRemarkEntryGetPassName(Remark), 6), "inline"); + EXPECT_EQ(checkStr(LLVMRemarkEntryGetRemarkName(Remark), 12), "NoDefinition"); + EXPECT_EQ(checkStr(LLVMRemarkEntryGetFunctionName(Remark), 3), "foo"); + LLVMRemarkDebugLocRef DL = LLVMRemarkEntryGetDebugLoc(Remark); + EXPECT_EQ(checkStr(LLVMRemarkDebugLocGetSourceFilePath(DL), 6), "file.c"); + EXPECT_EQ(LLVMRemarkDebugLocGetSourceLine(DL), 3U); + EXPECT_EQ(LLVMRemarkDebugLocGetSourceColumn(DL), 12U); + EXPECT_EQ(LLVMRemarkEntryGetHotness(Remark), 0U); + EXPECT_EQ(LLVMRemarkEntryGetNumArgs(Remark), 4U); + + unsigned ArgID = 0; + LLVMRemarkArgRef Arg = LLVMRemarkEntryGetFirstArg(Remark); + do { + switch (ArgID) { + case 0: + EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "Callee"); + EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 3), "bar"); + EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); + break; + case 1: + EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "String"); + EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 26), + " will not be inlined into "); + EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); + break; + case 2: { + EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "Caller"); + EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 3), "foo"); + LLVMRemarkDebugLocRef DL = LLVMRemarkArgGetDebugLoc(Arg); + EXPECT_EQ(checkStr(LLVMRemarkDebugLocGetSourceFilePath(DL), 6), "file.c"); + EXPECT_EQ(LLVMRemarkDebugLocGetSourceLine(DL), 2U); + EXPECT_EQ(LLVMRemarkDebugLocGetSourceColumn(DL), 0U); + break; + } + case 3: + EXPECT_EQ(checkStr(LLVMRemarkArgGetKey(Arg), 6), "String"); + EXPECT_EQ(checkStr(LLVMRemarkArgGetValue(Arg), 38), + " because its definition is unavailable"); + EXPECT_EQ(LLVMRemarkArgGetDebugLoc(Arg), nullptr); + break; + default: + break; + } + ++ArgID; + } while ((Arg = LLVMRemarkEntryGetNextArg(Arg, Remark))); + + EXPECT_EQ(LLVMRemarkParserGetNext(Parser), nullptr); + + EXPECT_FALSE(LLVMRemarkParserHasError(Parser)); + LLVMRemarkParserDispose(Parser); +}