diff options
Diffstat (limited to 'llvm/unittests/Remarks/BitstreamRemarksParsingTest.cpp')
-rw-r--r-- | llvm/unittests/Remarks/BitstreamRemarksParsingTest.cpp | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/llvm/unittests/Remarks/BitstreamRemarksParsingTest.cpp b/llvm/unittests/Remarks/BitstreamRemarksParsingTest.cpp new file mode 100644 index 00000000000..6234931b3be --- /dev/null +++ b/llvm/unittests/Remarks/BitstreamRemarksParsingTest.cpp @@ -0,0 +1,401 @@ +//===- unittests/Support/BitstreamRemarksParsingTest.cpp - Parsing 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 "llvm/Remarks/RemarkSerializer.h" +#include "gtest/gtest.h" + +using namespace llvm; + +template <size_t N> void parseGood(const char (&Buf)[N]) { + // 1. Parse the YAML remark -> FromYAMLRemark + // 2. Serialize it to bitstream -> BSStream + // 3. Parse it back -> FromBSRemark + // 4. Compare the remark objects + // + // This testing methodology has the drawback of relying on both the YAML + // remark parser and the bitstream remark serializer. It does simplify + // testing a lot, since working directly with bitstream is not that easy. + + // 1. + Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser = + remarks::createRemarkParser(remarks::Format::YAML, {Buf, N - 1}); + EXPECT_FALSE(errorToBool(MaybeParser.takeError())); + EXPECT_TRUE(*MaybeParser != nullptr); + + std::unique_ptr<remarks::Remark> FromYAMLRemark = nullptr; + remarks::RemarkParser &Parser = **MaybeParser; + Expected<std::unique_ptr<remarks::Remark>> Remark = Parser.next(); + EXPECT_FALSE(errorToBool(Remark.takeError())); // Check for parsing errors. + EXPECT_TRUE(*Remark != nullptr); // At least one remark. + // Keep the previous remark around. + FromYAMLRemark = std::move(*Remark); + Remark = Parser.next(); + Error E = Remark.takeError(); + EXPECT_TRUE(E.isA<remarks::EndOfFileError>()); + EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors. + + // 2. + remarks::StringTable BSStrTab; + BSStrTab.internalize(*FromYAMLRemark); + std::string BSBuf; + raw_string_ostream BSStream(BSBuf); + Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer = + remarks::createRemarkSerializer(remarks::Format::Bitstream, + remarks::SerializerMode::Standalone, + BSStream, std::move(BSStrTab)); + EXPECT_FALSE(errorToBool(BSSerializer.takeError())); + (*BSSerializer)->emit(*FromYAMLRemark); + + // 3. + Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser = + remarks::createRemarkParser(remarks::Format::Bitstream, BSStream.str()); + EXPECT_FALSE(errorToBool(MaybeBSParser.takeError())); + EXPECT_TRUE(*MaybeBSParser != nullptr); + + std::unique_ptr<remarks::Remark> FromBSRemark = nullptr; + remarks::RemarkParser &BSParser = **MaybeBSParser; + Expected<std::unique_ptr<remarks::Remark>> BSRemark = BSParser.next(); + EXPECT_FALSE(errorToBool(BSRemark.takeError())); // Check for parsing errors. + EXPECT_TRUE(*BSRemark != nullptr); // At least one remark. + // Keep the previous remark around. + FromBSRemark = std::move(*BSRemark); + BSRemark = BSParser.next(); + Error BSE = BSRemark.takeError(); + EXPECT_TRUE(BSE.isA<remarks::EndOfFileError>()); + EXPECT_TRUE(errorToBool(std::move(BSE))); // Check for parsing errors. + + EXPECT_EQ(*FromYAMLRemark, *FromBSRemark); +} + +TEST(BitstreamRemarks, 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" + ""); +} + +// Mandatory common part of a remark. +#define COMMON_REMARK "\nPass: inline\nName: NoDefinition\nFunction: foo\n\n" +// Test all the types. +TEST(BitstreamRemarks, 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 + +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(BitstreamRemarks, 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"; + + Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser = + remarks::createRemarkParser(remarks::Format::YAML, Buf); + EXPECT_FALSE(errorToBool(MaybeParser.takeError())); + EXPECT_TRUE(*MaybeParser != nullptr); + + remarks::RemarkParser &Parser = **MaybeParser; + Expected<std::unique_ptr<remarks::Remark>> MaybeRemark = Parser.next(); + EXPECT_FALSE( + errorToBool(MaybeRemark.takeError())); // Check for parsing errors. + EXPECT_TRUE(*MaybeRemark != nullptr); // At least one remark. + + const remarks::Remark &Remark = **MaybeRemark; + 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; + } + + MaybeRemark = Parser.next(); + Error E = MaybeRemark.takeError(); + EXPECT_TRUE(E.isA<remarks::EndOfFileError>()); + EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors. +} + +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(BitstreamRemarks, ContentsCAPI) { + remarks::StringTable BSStrTab; + remarks::Remark ToSerializeRemark; + ToSerializeRemark.RemarkType = remarks::Type::Missed; + ToSerializeRemark.PassName = "inline"; + ToSerializeRemark.RemarkName = "NoDefinition"; + ToSerializeRemark.FunctionName = "foo"; + ToSerializeRemark.Loc = remarks::RemarkLocation{"file.c", 3, 12}; + ToSerializeRemark.Hotness = 0; + ToSerializeRemark.Args.emplace_back(); + ToSerializeRemark.Args.back().Key = "Callee"; + ToSerializeRemark.Args.back().Val = "bar"; + ToSerializeRemark.Args.emplace_back(); + ToSerializeRemark.Args.back().Key = "String"; + ToSerializeRemark.Args.back().Val = " will not be inlined into "; + ToSerializeRemark.Args.emplace_back(); + ToSerializeRemark.Args.back().Key = "Caller"; + ToSerializeRemark.Args.back().Val = "foo"; + ToSerializeRemark.Args.back().Loc = remarks::RemarkLocation{"file.c", 2, 0}; + ToSerializeRemark.Args.emplace_back(); + ToSerializeRemark.Args.back().Key = "String"; + ToSerializeRemark.Args.back().Val = " because its definition is unavailable"; + BSStrTab.internalize(ToSerializeRemark); + std::string BSBuf; + raw_string_ostream BSStream(BSBuf); + Expected<std::unique_ptr<remarks::RemarkSerializer>> BSSerializer = + remarks::createRemarkSerializer(remarks::Format::Bitstream, + remarks::SerializerMode::Standalone, + BSStream, std::move(BSStrTab)); + EXPECT_FALSE(errorToBool(BSSerializer.takeError())); + (*BSSerializer)->emit(ToSerializeRemark); + + StringRef Buf = BSStream.str(); + LLVMRemarkParserRef Parser = + LLVMRemarkParserCreateBitstream(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))); + + LLVMRemarkEntryDispose(Remark); + + EXPECT_EQ(LLVMRemarkParserGetNext(Parser), nullptr); + + EXPECT_FALSE(LLVMRemarkParserHasError(Parser)); + LLVMRemarkParserDispose(Parser); +} + +static void parseBad(StringRef Input, const char *ErrorMsg) { + Expected<std::unique_ptr<remarks::RemarkParser>> MaybeBSParser = + remarks::createRemarkParser(remarks::Format::Bitstream, Input); + EXPECT_FALSE(errorToBool(MaybeBSParser.takeError())); + EXPECT_TRUE(*MaybeBSParser != nullptr); + + remarks::RemarkParser &BSParser = **MaybeBSParser; + Expected<std::unique_ptr<remarks::Remark>> BSRemark = BSParser.next(); + EXPECT_EQ(ErrorMsg, toString(BSRemark.takeError())); // Expect an error. +} + +TEST(BitstreamRemarks, ParsingEmpty) { + parseBad(StringRef(), "End of file reached."); +} + +TEST(BitstreamRemarks, ParsingBadMagic) { + parseBad("KRMR", "Unknown magic number: expecting RMRK, got KRMR."); +} + +// Testing malformed bitstream is not easy. We would need to replace bytes in +// the stream to create malformed and unknown records and blocks. There is no +// textual format for bitstream that can be decoded, modified and encoded +// back. + +// FIXME: Add tests for the following error messages: +// * Error while parsing META_BLOCK: malformed record entry +// (RECORD_META_CONTAINER_INFO). +// * Error while parsing META_BLOCK: malformed record entry +// (RECORD_META_REMARK_VERSION). +// * Error while parsing META_BLOCK: malformed record entry +// (RECORD_META_STRTAB). +// * Error while parsing META_BLOCK: malformed record entry +// (RECORD_META_EXTERNAL_FILE). +// * Error while parsing META_BLOCK: unknown record entry (NUM). +// * Error while parsing REMARK_BLOCK: malformed record entry +// (RECORD_REMARK_HEADER). +// * Error while parsing REMARK_BLOCK: malformed record entry +// (RECORD_REMARK_DEBUG_LOC). +// * Error while parsing REMARK_BLOCK: malformed record entry +// (RECORD_REMARK_HOTNESS). +// * Error while parsing REMARK_BLOCK: malformed record entry +// (RECORD_REMARK_ARG_WITH_DEBUGLOC). +// * Error while parsing REMARK_BLOCK: malformed record entry +// (RECORD_REMARK_ARG_WITHOUT_DEBUGLOC). +// * Error while parsing REMARK_BLOCK: unknown record entry (NUM). +// * Error while parsing META_BLOCK: expecting [ENTER_SUBBLOCO, META_BLOCK, +// ...]. +// * Error while entering META_BLOCK. +// * Error while parsing META_BLOCK: expecting records. +// * Error while parsing META_BLOCK: unterminated block. +// * Error while parsing REMARK_BLOCK: expecting [ENTER_SUBBLOCO, REMARK_BLOCK, +// ...]. +// * Error while entering REMARK_BLOCK. +// * Error while parsing REMARK_BLOCK: expecting records. +// * Error while parsing REMARK_BLOCK: unterminated block. +// * Error while parsing BLOCKINFO_BLOCK: expecting [ENTER_SUBBLOCK, +// BLOCKINFO_BLOCK, ...]. +// * Error while parsing BLOCKINFO_BLOCK. +// * Unexpected error while parsing bitstream. +// * Expecting META_BLOCK after the BLOCKINFO_BLOCK. +// * Error while parsing BLOCK_META: missing container version. +// * Error while parsing BLOCK_META: invalid container type. +// * Error while parsing BLOCK_META: missing container type. +// * Error while parsing BLOCK_META: missing string table. +// * Error while parsing BLOCK_META: missing remark version. +// * Error while parsing BLOCK_META: missing external file path. +// * Error while parsing external file's BLOCK_META: wrong container type. +// * Error while parsing external file's BLOCK_META: mismatching versions: +// original meta: NUM, external file meta: NUM. +// * Error while parsing BLOCK_REMARK: missing string table. +// * Error while parsing BLOCK_REMARK: missing remark type. +// * Error while parsing BLOCK_REMARK: unknown remark type. +// * Error while parsing BLOCK_REMARK: missing remark name. +// * Error while parsing BLOCK_REMARK: missing remark pass. +// * Error while parsing BLOCK_REMARK: missing remark function name. +// * Error while parsing BLOCK_REMARK: missing key in remark argument. +// * Error while parsing BLOCK_REMARK: missing value in remark argument. |