Reland "[Remarks] Add a new Remark / RemarkParser abstraction"

This adds a Remark class that allows us to share code when working with
remarks.

The C API has been updated to reflect this. Instead of the parser
generating C structs, it's now using a C++ object that is used through
opaque pointers in C. This gives us much more flexibility on what
changes we can make to the internal state of the object and interacts
much better with scenarios where the library is used through dlopen.

* C API updates:
  * move from C structs to opaque pointers and functions
  * the remark type is now an enum instead of a string
* unit tests updates:
  * use mostly the C++ API
  * keep one test for the C API
  * rename to YAMLRemarksParsingTest
* a typo was fixed: AnalysisFPCompute -> AnalysisFPCommute.
* a new error message was added: "expected a remark tag."
* llvm-opt-report has been updated to use the C++ parser instead of the
C API

Differential Revision: https://reviews.llvm.org/D59049

Original llvm-svn: 356491

llvm-svn: 356519
This commit is contained in:
Francis Visoiu Mistrih 2019-03-19 21:11:07 +00:00
parent a0c4fb1375
commit aad252295b
14 changed files with 1494 additions and 856 deletions

View File

@ -33,86 +33,201 @@ extern "C" {
#define REMARKS_API_VERSION 0 #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 * String containing a buffer and a length. The buffer is not guaranteed to be
* zero-terminated. * zero-terminated.
* *
* \since REMARKS_API_VERSION=0 * \since REMARKS_API_VERSION=0
*/ */
typedef struct { typedef struct LLVMRemarkOpaqueString *LLVMRemarkStringRef;
const char *Str;
uint32_t Len; /**
} 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. * DebugLoc containing File, Line and Column.
* *
* \since REMARKS_API_VERSION=0 * \since REMARKS_API_VERSION=0
*/ */
typedef struct { typedef struct LLVMRemarkOpaqueDebugLoc *LLVMRemarkDebugLocRef;
// File:
LLVMRemarkStringRef SourceFile; /**
// Line: * Return the path to the source file for a debug location.
uint32_t SourceLineNumber; *
// Column: * \since REMARKS_API_VERSION=0
uint32_t SourceColumnNumber; */
} LLVMRemarkDebugLoc; 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 * 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. * is a symbol that names a function.
* *
* \since REMARKS_API_VERSION=0 * \since REMARKS_API_VERSION=0
*/ */
typedef struct { typedef struct LLVMRemarkOpaqueArg *LLVMRemarkArgRef;
// e.g. "Callee"
LLVMRemarkStringRef Key;
// e.g. "malloc"
LLVMRemarkStringRef Value;
// "DebugLoc": Optional
LLVMRemarkDebugLoc DebugLoc;
} LLVMRemarkArg;
/** /**
* 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 * \since REMARKS_API_VERSION=0
*/ */
typedef struct { extern LLVMRemarkStringRef LLVMRemarkArgGetKey(LLVMRemarkArgRef Arg);
// e.g. !Missed, !Passed
LLVMRemarkStringRef RemarkType;
// "Pass": Required
LLVMRemarkStringRef PassName;
// "Name": Required
LLVMRemarkStringRef RemarkName;
// "Function": Required
LLVMRemarkStringRef FunctionName;
// "DebugLoc": Optional /**
LLVMRemarkDebugLoc DebugLoc; * Returns the value of an argument. This is a string that can contain newlines.
// "Hotness": Optional *
uint32_t Hotness; * \since REMARKS_API_VERSION=0
// "Args": Optional. It is an array of `num_args` elements. */
uint32_t NumArgs; extern LLVMRemarkStringRef LLVMRemarkArgGetValue(LLVMRemarkArgRef Arg);
LLVMRemarkArg *Args;
} LLVMRemarkEntry; /**
* 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; typedef struct LLVMRemarkOpaqueParser *LLVMRemarkParserRef;
/** /**
* Creates a remark parser that can be used to read and parse the buffer located * Creates a remark parser that can be used to parse the buffer located in \p
* in \p Buf of size \p Size. * 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 * This function should be paired with LLVMRemarkParserDispose() to avoid
* leaking resources. * leaking resources.
* *
* \since REMARKS_API_VERSION=0 * \since REMARKS_API_VERSION=0
*/ */
extern LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf, extern LLVMRemarkParserRef LLVMRemarkParserCreateYAML(const void *Buf,
uint64_t Size); uint64_t Size);
/** /**
@ -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 * The value pointed to by the return value is invalidated by the next call to
* LLVMRemarkParserGetNext(). * 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`. * 1) LLVMRemarkParserHasError() will return `1`.
* *
@ -134,18 +249,16 @@ extern LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf,
* *
* 1) An argument is invalid. * 1) An argument is invalid.
* *
* 2) There is a YAML parsing error. This type of error aborts parsing * 2) There is a parsing error. This can occur on things like malformed YAML.
* immediately and returns `1`. It can occur on malformed YAML.
* *
* 3) Remark parsing error. If this type of error occurs, the parser won't call * 3) There is a Remark semantic error. This can occur on well-formed files with
* the handler and will continue to the next one. It can occur on malformed * missing or extra fields.
* remarks, like missing or extra fields in the file.
* *
* Here is a quick example of the usage: * Here is a quick example of the usage:
* *
* ``` * ```
* LLVMRemarkParserRef Parser = LLVMRemarkParserCreate(Buf, Size); * LLVMRemarkParserRef Parser = LLVMRemarkParserCreateYAML(Buf, Size);
* LLVMRemarkEntry *Remark = NULL; * LLVMRemarkEntryRef Remark = NULL;
* while ((Remark == LLVMRemarkParserGetNext(Parser))) { * while ((Remark == LLVMRemarkParserGetNext(Parser))) {
* // use Remark * // use Remark
* } * }
@ -155,7 +268,7 @@ extern LLVMRemarkParserRef LLVMRemarkParserCreate(const void *Buf,
* *
* \since REMARKS_API_VERSION=0 * \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. * 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); extern void LLVMRemarkParserDispose(LLVMRemarkParserRef Parser);
/** /**
* Returns the version of the remarks dylib. * Returns the version of the remarks library.
* *
* \since REMARKS_API_VERSION=0 * \since REMARKS_API_VERSION=0
*/ */

View File

@ -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 <string>
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<RemarkLocation> 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<RemarkLocation> Loc;
/// If profile information is available, this is the number of times the
/// corresponding code was executed in a profile instrumentation run.
Optional<uint64_t> Hotness;
/// Arguments collected via the streaming interface.
ArrayRef<Argument> 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 */

View File

@ -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 <memory>
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<ParserImpl> 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<const Remark *> getNext() const;
};
} // end namespace remarks
} // end namespace llvm
#endif /* LLVM_REMARKS_REMARK_PARSER_H */

View File

@ -1,3 +1,5 @@
add_llvm_library(LLVMRemarks add_llvm_library(LLVMRemarks
Remark.cpp
RemarkParser.cpp RemarkParser.cpp
YAMLRemarkParser.cpp
) )

128
lib/Remarks/Remark.cpp Normal file
View File

@ -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<RemarkLocation> &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<LLVMRemarkType>(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<RemarkLocation> &Loc = unwrap(Remark)->Loc)
return wrap(&*Loc);
return nullptr;
}
extern "C" uint64_t LLVMRemarkEntryGetHotness(LLVMRemarkEntryRef Remark) {
if (const Optional<uint64_t> &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<Argument> Args = unwrap(Remark)->Args;
// No arguments to iterate on.
if (Args.empty())
return NULL;
return reinterpret_cast<LLVMRemarkArgRef>(
const_cast<Argument *>(Args.begin()));
}
extern "C" LLVMRemarkArgRef
LLVMRemarkEntryGetNextArg(LLVMRemarkArgRef ArgIt, LLVMRemarkEntryRef Remark) {
// No more arguments to iterate on.
if (ArgIt == NULL)
return NULL;
auto It = (ArrayRef<Argument>::const_iterator)ArgIt;
auto Next = std::next(It);
if (Next == unwrap(Remark)->Args.end())
return NULL;
return reinterpret_cast<LLVMRemarkArgRef>(const_cast<Argument *>(Next));
}

View File

@ -11,355 +11,104 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "llvm/Remarks/RemarkParser.h"
#include "YAMLRemarkParser.h"
#include "llvm-c/Remarks.h" #include "llvm-c/Remarks.h"
#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLExtras.h"
#include "llvm/Support/SourceMgr.h" #include "llvm/Support/CBindingWrapping.h"
#include "llvm/Support/YAMLTraits.h"
using namespace llvm; using namespace llvm;
using namespace llvm::remarks;
namespace { Parser::Parser(StringRef Buf) : Impl(llvm::make_unique<YAMLParserImpl>(Buf)) {}
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<LLVMRemarkEntry> LastRemark;
/// Temporary parsing buffer for the arguments.
SmallVector<LLVMRemarkArg, 8> 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<LLVMRemarkArg> *Args;
StringRef Type;
StringRef Pass;
StringRef Name;
StringRef Function;
/// Optional.
Optional<StringRef> File;
Optional<unsigned> Line;
Optional<unsigned> Column;
Optional<unsigned> Hotness;
ParseState(SmallVectorImpl<LLVMRemarkArg> &Args) : Args(&Args) {} Parser::~Parser() = default;
/// Use Args only as a **temporary** buffer.
~ParseState() { Args->clear(); }
};
ParseState State; static Expected<const Remark *> getNextYAML(YAMLParserImpl &Impl) {
YAMLRemarkParser &YAMLParser = Impl.YAMLParser;
/// 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<unsigned> &Result, yaml::KeyValueNode &Node);
/// Parse a debug location.
Error parseDebugLoc(Optional<StringRef> &File, Optional<unsigned> &Line,
Optional<unsigned> &Column, yaml::KeyValueNode &Node);
/// Parse an argument.
Error parseArg(SmallVectorImpl<LLVMRemarkArg> &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<YAMLRemarkParser *>(Ctx);
Diag.print(/*ProgName=*/nullptr, Parser->ErrorStream, /*ShowColors*/ false,
/*ShowKindLabels*/ true);
}
};
class ParseError : public ErrorInfo<ParseError> {
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<uint32_t>(Str.size())};
}
Error YAMLRemarkParser::parseKey(StringRef &Result, yaml::KeyValueNode &Node) {
auto *Key = dyn_cast<yaml::ScalarNode>(Node.getKey());
if (!Key)
return make_error<ParseError>("key is not a string.", Node);
Result = Key->getRawValue();
return Error::success();
}
Error YAMLRemarkParser::parseValue(StringRef &Result,
yaml::KeyValueNode &Node) {
auto *Value = dyn_cast<yaml::ScalarNode>(Node.getValue());
if (!Value)
return make_error<ParseError>("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<unsigned> &Result,
yaml::KeyValueNode &Node) {
SmallVector<char, 4> Tmp;
auto *Value = dyn_cast<yaml::ScalarNode>(Node.getValue());
if (!Value)
return make_error<ParseError>("expected a value of scalar type.", Node);
unsigned UnsignedValue = 0;
if (Value->getValue(Tmp).getAsInteger(10, UnsignedValue))
return make_error<ParseError>("expected a value of integer type.", *Value);
Result = UnsignedValue;
return Error::success();
}
Error YAMLRemarkParser::parseDebugLoc(Optional<StringRef> &File,
Optional<unsigned> &Line,
Optional<unsigned> &Column,
yaml::KeyValueNode &Node) {
auto *DebugLoc = dyn_cast<yaml::MappingNode>(Node.getValue());
if (!DebugLoc)
return make_error<ParseError>("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<ParseError>("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<ParseError>("DebugLoc node incomplete.", Node);
return Error::success();
}
Error YAMLRemarkParser::parseArg(SmallVectorImpl<LLVMRemarkArg> &Args,
yaml::Node &Node) {
auto *ArgMap = dyn_cast<yaml::MappingNode>(&Node);
if (!ArgMap)
return make_error<ParseError>("expected a value of mapping type.", Node);
StringRef ValueStr;
StringRef KeyStr;
Optional<StringRef> File;
Optional<unsigned> Line;
Optional<unsigned> 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<ParseError>(
"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<ParseError>(
"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<ParseError>("argument key is missing.", *ArgMap);
if (ValueStr.empty())
return make_error<ParseError>("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<yaml::MappingNode>(Remark.getRoot());
if (!Root)
return make_error<ParseError>("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<yaml::SequenceNode>(RemarkField.getValue());
if (!Args)
return make_error<ParseError>("wrong value type for key.", RemarkField);
for (yaml::Node &Arg : *Args)
if (Error E = parseArg(*State.Args, Arg))
return E;
} else {
return make_error<ParseError>("unknown key.", RemarkField);
}
}
// If the YAML parsing failed, don't even continue parsing. We might
// encounter malformed YAML.
if (Stream.failed())
return make_error<ParseError>("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<ParseError>("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<uint32_t>(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<const char *>(Buf), Size)));
}
extern "C" LLVMRemarkEntry *
LLVMRemarkParserGetNext(LLVMRemarkParserRef Parser) {
YAMLRemarkParser &TheParser = *unwrap(Parser);
// Check for EOF. // Check for EOF.
if (TheParser.HadAnyErrors || TheParser.DI == TheParser.Stream.end()) if (Impl.YAMLIt == Impl.YAMLParser.Stream.end())
return nullptr; return nullptr;
auto CurrentIt = Impl.YAMLIt;
// Try to parse an entry. // Try to parse an entry.
if (Error E = TheParser.parseYAMLElement(*TheParser.DI)) { if (Error E = YAMLParser.parseYAMLElement(*CurrentIt)) {
handleAllErrors(std::move(E), [&](const ParseError &PE) { // Set the iterator to the end, in case the user calls getNext again.
TheParser.Stream.printError(&PE.getNode(), Impl.YAMLIt = Impl.YAMLParser.Stream.end();
Twine(PE.getMessage()) + Twine('\n')); return std::move(E);
TheParser.HadAnyErrors = true;
});
return nullptr;
} }
// Move on. // Move on.
++TheParser.DI; ++Impl.YAMLIt;
// Return the just-parsed remark. // Return the just-parsed remark.
if (Optional<LLVMRemarkEntry> &Entry = TheParser.LastRemark) if (const Optional<YAMLRemarkParser::ParseState> &State = YAMLParser.State)
return &*Entry; return &State->TheRemark;
else
return createStringError(std::make_error_code(std::errc::invalid_argument),
"unexpected error while parsing.");
}
Expected<const Remark *> Parser::getNext() const {
if (auto *Impl = dyn_cast<YAMLParserImpl>(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<const char *>(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<const remarks::Remark *> RemarkOrErr = TheParser.getNext();
if (!RemarkOrErr) {
// Error during parsing.
if (auto *Impl = dyn_cast<remarks::YAMLParserImpl>(TheParser.Impl.get()))
handleYAMLError(*Impl, RemarkOrErr.takeError());
else
llvm_unreachable("unkown parser implementation.");
return nullptr; return nullptr;
} }
if (*RemarkOrErr == nullptr)
return nullptr;
// Valid remark.
return wrap(*RemarkOrErr);
}
extern "C" LLVMBool LLVMRemarkParserHasError(LLVMRemarkParserRef Parser) { extern "C" LLVMBool LLVMRemarkParserHasError(LLVMRemarkParserRef Parser) {
return unwrap(Parser)->HadAnyErrors; if (auto *Impl =
dyn_cast<remarks::YAMLParserImpl>(unwrap(Parser)->Impl.get()))
return Impl->HasErrors;
llvm_unreachable("unkown parser implementation.");
} }
extern "C" const char * extern "C" const char *
LLVMRemarkParserGetErrorMessage(LLVMRemarkParserRef Parser) { LLVMRemarkParserGetErrorMessage(LLVMRemarkParserRef Parser) {
return unwrap(Parser)->ErrorStream.str().c_str(); if (auto *Impl =
dyn_cast<remarks::YAMLParserImpl>(unwrap(Parser)->Impl.get()))
return Impl->YAMLParser.ErrorStream.str().c_str();
llvm_unreachable("unkown parser implementation.");
} }
extern "C" void LLVMRemarkParserDispose(LLVMRemarkParserRef Parser) { extern "C" void LLVMRemarkParserDispose(LLVMRemarkParserRef Parser) {

View File

@ -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 */

View File

@ -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<yaml::ScalarNode>(Node.getKey())) {
Result = Key->getRawValue();
return Error::success();
}
return make_error<YAMLParseError>("key is not a string.", Node);
}
template <typename T>
Error YAMLRemarkParser::parseStr(T &Result, yaml::KeyValueNode &Node) {
auto *Value = dyn_cast<yaml::ScalarNode>(Node.getValue());
if (!Value)
return make_error<YAMLParseError>("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 <typename T>
Error YAMLRemarkParser::parseUnsigned(T &Result, yaml::KeyValueNode &Node) {
SmallVector<char, 4> Tmp;
auto *Value = dyn_cast<yaml::ScalarNode>(Node.getValue());
if (!Value)
return make_error<YAMLParseError>("expected a value of scalar type.", Node);
unsigned UnsignedValue = 0;
if (Value->getValue(Tmp).getAsInteger(10, UnsignedValue))
return make_error<YAMLParseError>("expected a value of integer type.",
*Value);
Result = UnsignedValue;
return Error::success();
}
Error YAMLRemarkParser::parseType(Type &Result, yaml::MappingNode &Node) {
auto Type = StringSwitch<remarks::Type>(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<YAMLParseError>("expected a remark tag.", Node);
Result = Type;
return Error::success();
}
Error YAMLRemarkParser::parseDebugLoc(Optional<RemarkLocation> &Result,
yaml::KeyValueNode &Node) {
auto *DebugLoc = dyn_cast<yaml::MappingNode>(Node.getValue());
if (!DebugLoc)
return make_error<YAMLParseError>("expected a value of mapping type.",
Node);
Optional<StringRef> File;
Optional<unsigned> Line;
Optional<unsigned> 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<YAMLParseError>("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<YAMLParseError>("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<yaml::SequenceNode>(RemarkField.getValue());
if (!Args)
return make_error<YAMLParseError>("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<YAMLParseError>("unknown key.", RemarkField);
}
return Error::success();
}
Error YAMLRemarkParser::parseArg(SmallVectorImpl<Argument> &Args,
yaml::Node &Node) {
auto *ArgMap = dyn_cast<yaml::MappingNode>(&Node);
if (!ArgMap)
return make_error<YAMLParseError>("expected a value of mapping type.",
Node);
StringRef KeyStr;
StringRef ValueStr;
Optional<RemarkLocation> Loc;
for (yaml::KeyValueNode &ArgEntry : *ArgMap)
if (Error E = parseArgEntry(ArgEntry, KeyStr, ValueStr, Loc))
return E;
if (KeyStr.empty())
return make_error<YAMLParseError>("argument key is missing.", *ArgMap);
if (ValueStr.empty())
return make_error<YAMLParseError>("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<RemarkLocation> &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<YAMLParseError>(
"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<YAMLParseError>(
"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<yaml::MappingNode>(YAMLRoot);
if (!Root)
return make_error<YAMLParseError>("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<YAMLParseError>("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<YAMLParseError>("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<YAMLRemarkParser *>(Ctx);
Diag.print(/*ProgName=*/nullptr, Parser->ErrorStream, /*ShowColors*/ false,
/*ShowKindLabels*/ true);
}

View File

@ -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 <string>
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<Argument, 8> 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<Argument> &Args;
Remark TheRemark;
ParseState(SmallVectorImpl<Argument> &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<ParseState> 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 <typename T> Error parseStr(T &Result, yaml::KeyValueNode &Node);
/// Parse one value to an unsigned.
template <typename T>
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<RemarkLocation> &Result,
yaml::KeyValueNode &Node);
/// Parse a remark field and update the parsing state.
Error parseRemarkField(yaml::KeyValueNode &RemarkField);
/// Parse an argument.
Error parseArg(SmallVectorImpl<Argument> &TmpArgs, yaml::Node &Node);
/// Parse an entry from the contents of an argument.
Error parseArgEntry(yaml::KeyValueNode &ArgEntry, StringRef &KeyStr,
StringRef &ValueStr, Optional<RemarkLocation> &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<YAMLParseError> {
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 */

View File

@ -15,6 +15,7 @@
#include "llvm-c/Remarks.h" #include "llvm-c/Remarks.h"
#include "llvm/Demangle/Demangle.h" #include "llvm/Demangle/Demangle.h"
#include "llvm/Remarks/RemarkParser.h"
#include "llvm/Support/CommandLine.h" #include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h" #include "llvm/Support/Error.h"
#include "llvm/Support/ErrorOr.h" #include "llvm/Support/ErrorOr.h"
@ -151,40 +152,44 @@ static bool readLocationInfo(LocationInfoTy &LocationInfo) {
return false; return false;
} }
StringRef Buffer = (*Buf)->getBuffer(); remarks::Parser Parser((*Buf)->getBuffer());
LLVMRemarkParserRef Parser =
LLVMRemarkParserCreate(Buffer.data(), Buffer.size());
LLVMRemarkEntry *Remark = nullptr; while (true) {
while ((Remark = LLVMRemarkParserGetNext(Parser))) { Expected<const remarks::Remark *> RemarkOrErr = Parser.getNext();
bool Transformed = if (!RemarkOrErr) {
StringRef(Remark->RemarkType.Str, Remark->RemarkType.Len) == "!Passed"; handleAllErrors(RemarkOrErr.takeError(), [&](const ErrorInfoBase &PE) {
StringRef Pass(Remark->PassName.Str, Remark->PassName.Len); PE.log(WithColor::error());
StringRef File(Remark->DebugLoc.SourceFile.Str, });
Remark->DebugLoc.SourceFile.Len); return false;
StringRef Function(Remark->FunctionName.Str, Remark->FunctionName.Len); }
uint32_t Line = Remark->DebugLoc.SourceLineNumber; if (!*RemarkOrErr) // End of file.
uint32_t Column = Remark->DebugLoc.SourceColumnNumber; break;
ArrayRef<LLVMRemarkArg> Args(Remark->Args, Remark->NumArgs);
const remarks::Remark &Remark = **RemarkOrErr;
bool Transformed = Remark.RemarkType == remarks::Type::Passed;
int VectorizationFactor = 1; int VectorizationFactor = 1;
int InterleaveCount = 1; int InterleaveCount = 1;
int UnrollCount = 1; int UnrollCount = 1;
for (const LLVMRemarkArg &Arg : Args) { for (const remarks::Argument &Arg : Remark.Args) {
StringRef ArgKeyName(Arg.Key.Str, Arg.Key.Len); if (Arg.Key == "VectorizationFactor")
StringRef ArgValue(Arg.Value.Str, Arg.Value.Len); Arg.Val.getAsInteger(10, VectorizationFactor);
if (ArgKeyName == "VectorizationFactor") else if (Arg.Key == "InterleaveCount")
ArgValue.getAsInteger(10, VectorizationFactor); Arg.Val.getAsInteger(10, InterleaveCount);
else if (ArgKeyName == "InterleaveCount") else if (Arg.Key == "UnrollCount")
ArgValue.getAsInteger(10, InterleaveCount); Arg.Val.getAsInteger(10, UnrollCount);
else if (ArgKeyName == "UnrollCount")
ArgValue.getAsInteger(10, UnrollCount);
} }
if (Line < 1 || File.empty()) const Optional<remarks::RemarkLocation> &Loc = Remark.Loc;
if (!Loc)
continue; continue;
StringRef File = Loc->SourceFilePath;
unsigned Line = Loc->SourceLine;
unsigned Column = Loc->SourceColumn;
// We track information on both actual and potential transformations. This // We track information on both actual and potential transformations. This
// way, if there are multiple possible things on a line that are, or could // 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. // have been transformed, we can indicate that explicitly in the output.
@ -194,27 +199,22 @@ static bool readLocationInfo(LocationInfoTy &LocationInfo) {
LLII.Transformed = true; LLII.Transformed = true;
}; };
if (Pass == "inline") { if (Remark.PassName == "inline") {
auto &LI = LocationInfo[File][Line][Function][Column]; auto &LI = LocationInfo[File][Line][Remark.FunctionName][Column];
UpdateLLII(LI.Inlined); UpdateLLII(LI.Inlined);
} else if (Pass == "loop-unroll") { } else if (Remark.PassName == "loop-unroll") {
auto &LI = LocationInfo[File][Line][Function][Column]; auto &LI = LocationInfo[File][Line][Remark.FunctionName][Column];
LI.UnrollCount = UnrollCount; LI.UnrollCount = UnrollCount;
UpdateLLII(LI.Unrolled); UpdateLLII(LI.Unrolled);
} else if (Pass == "loop-vectorize") { } else if (Remark.PassName == "loop-vectorize") {
auto &LI = LocationInfo[File][Line][Function][Column]; auto &LI = LocationInfo[File][Line][Remark.FunctionName][Column];
LI.VectorizationFactor = VectorizationFactor; LI.VectorizationFactor = VectorizationFactor;
LI.InterleaveCount = InterleaveCount; LI.InterleaveCount = InterleaveCount;
UpdateLLII(LI.Vectorized); UpdateLLII(LI.Vectorized);
} }
} }
bool HasError = LLVMRemarkParserHasError(Parser); return true;
if (HasError)
WithColor::error() << LLVMRemarkParserGetErrorMessage(Parser) << "\n";
LLVMRemarkParserDispose(Parser);
return !HasError;
} }
static bool writeReport(LocationInfoTy &LocationInfo) { static bool writeReport(LocationInfoTy &LocationInfo) {

View File

@ -1,4 +1,21 @@
LLVMRemarkParserCreate LLVMRemarkStringGetData
LLVMRemarkStringGetLen
LLVMRemarkDebugLocGetSourceFilePath
LLVMRemarkDebugLocGetSourceLine
LLVMRemarkDebugLocGetSourceColumn
LLVMRemarkArgGetKey
LLVMRemarkArgGetValue
LLVMRemarkArgGetDebugLoc
LLVMRemarkEntryGetType
LLVMRemarkEntryGetPassName
LLVMRemarkEntryGetRemarkName
LLVMRemarkEntryGetFunctionName
LLVMRemarkEntryGetDebugLoc
LLVMRemarkEntryGetHotness
LLVMRemarkEntryGetNumArgs
LLVMRemarkEntryGetFirstArg
LLVMRemarkEntryGetNextArg
LLVMRemarkParserCreateYAML
LLVMRemarkParserGetNext LLVMRemarkParserGetNext
LLVMRemarkParserHasError LLVMRemarkParserHasError
LLVMRemarkParserGetErrorMessage LLVMRemarkParserGetErrorMessage

View File

@ -4,5 +4,5 @@ set(LLVM_LINK_COMPONENTS
) )
add_llvm_unittest(RemarksTests add_llvm_unittest(RemarksTests
RemarksParsingTest.cpp YAMLRemarksParsingTest.cpp
) )

View File

@ -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 <size_t N> 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 <size_t N>
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);
}

View File

@ -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 <size_t N> void parseGood(const char (&Buf)[N]) {
remarks::Parser Parser({Buf, N - 1});
Expected<const remarks::Remark *> 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 <size_t N>
bool parseExpectError(const char (&Buf)[N], const char *Error) {
remarks::Parser Parser({Buf, N - 1});
Expected<const remarks::Remark *> 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<const remarks::Remark *> 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);
}