mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-22 18:54:02 +01:00
[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 llvm-svn: 356491
This commit is contained in:
parent
47424b8478
commit
de75d7efaf
@ -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
|
||||
*/
|
||||
|
98
include/llvm/Remarks/Remark.h
Normal file
98
include/llvm/Remarks/Remark.h
Normal 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 */
|
46
include/llvm/Remarks/RemarkParser.h
Normal file
46
include/llvm/Remarks/RemarkParser.h
Normal 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 */
|
@ -1,3 +1,5 @@
|
||||
add_llvm_library(LLVMRemarks
|
||||
Remark.cpp
|
||||
RemarkParser.cpp
|
||||
YAMLRemarkParser.cpp
|
||||
)
|
||||
|
128
lib/Remarks/Remark.cpp
Normal file
128
lib/Remarks/Remark.cpp
Normal 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));
|
||||
}
|
@ -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<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;
|
||||
Parser::Parser(StringRef Buf) : Impl(llvm::make_unique<YAMLParserImpl>(Buf)) {}
|
||||
|
||||
ParseState(SmallVectorImpl<LLVMRemarkArg> &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<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);
|
||||
static Expected<const Remark *> 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<LLVMRemarkEntry> &Entry = TheParser.LastRemark)
|
||||
return &*Entry;
|
||||
return nullptr;
|
||||
if (const Optional<YAMLRemarkParser::ParseState> &State = YAMLParser.State)
|
||||
return &State->Remark;
|
||||
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'));
|
||||
Impl.HasErrors = true;
|
||||
},
|
||||
[&](const StringError &PE) { PE.log(Impl.YAMLParser.ErrorStream); });
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<remarks::YAMLParserImpl>(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<remarks::YAMLParserImpl>(unwrap(Parser)->Impl.get()))
|
||||
return Impl->YAMLParser.ErrorStream.str().c_str();
|
||||
llvm_unreachable("unkown parser implementation.");
|
||||
}
|
||||
|
||||
extern "C" void LLVMRemarkParserDispose(LLVMRemarkParserRef Parser) {
|
||||
|
29
lib/Remarks/RemarkParserImpl.h
Normal file
29
lib/Remarks/RemarkParserImpl.h
Normal 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 Kind;
|
||||
};
|
||||
} // end namespace remarks
|
||||
} // end namespace llvm
|
||||
|
||||
#endif /* LLVM_REMARKS_REMARK_PARSER_IMPL_H */
|
261
lib/Remarks/YAMLRemarkParser.cpp
Normal file
261
lib/Remarks/YAMLRemarkParser.cpp
Normal file
@ -0,0 +1,261 @@
|
||||
//===- 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->Remark.PassName, RemarkField))
|
||||
return E;
|
||||
} else if (KeyName == "Name") {
|
||||
if (Error E = parseStr(State->Remark.RemarkName, RemarkField))
|
||||
return E;
|
||||
} else if (KeyName == "Function") {
|
||||
if (Error E = parseStr(State->Remark.FunctionName, RemarkField))
|
||||
return E;
|
||||
} else if (KeyName == "Hotness") {
|
||||
State->Remark.Hotness = 0;
|
||||
if (Error E = parseUnsigned(*State->Remark.Hotness, RemarkField))
|
||||
return E;
|
||||
} else if (KeyName == "DebugLoc") {
|
||||
if (Error E = parseDebugLoc(State->Remark.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->Remark.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->Remark.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->Remark.RemarkType == Type::Unknown ||
|
||||
State->Remark.PassName.empty() || State->Remark.RemarkName.empty() ||
|
||||
State->Remark.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);
|
||||
}
|
136
lib/Remarks/YAMLRemarkParser.h
Normal file
136
lib/Remarks/YAMLRemarkParser.h
Normal 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 Remark;
|
||||
|
||||
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->Kind == ParserImpl::Kind::YAML;
|
||||
}
|
||||
};
|
||||
} // end namespace remarks
|
||||
} // end namespace llvm
|
||||
|
||||
#endif /* LLVM_REMARKS_YAML_REMARK_PARSER_H */
|
@ -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<LLVMRemarkArg> Args(Remark->Args, Remark->NumArgs);
|
||||
while (true) {
|
||||
Expected<const remarks::Remark *> 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<remarks::RemarkLocation> &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) {
|
||||
|
@ -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
|
||||
|
@ -4,5 +4,5 @@ set(LLVM_LINK_COMPONENTS
|
||||
)
|
||||
|
||||
add_llvm_unittest(RemarksTests
|
||||
RemarksParsingTest.cpp
|
||||
YAMLRemarksParsingTest.cpp
|
||||
)
|
||||
|
@ -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);
|
||||
}
|
494
unittests/Remarks/YAMLRemarksParsingTest.cpp
Normal file
494
unittests/Remarks/YAMLRemarksParsingTest.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user