1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-10-18 10:32:48 +02:00

[Remarks] Add support for linking remarks

Remarks are usually emitted per-TU, and for generating a standalone
remark file that can be shipped with the linked binary we need some kind
of tool to merge everything together.

The remarks::RemarkLinker class takes care of this and:

* Deduplicates remarks
* Filters remarks with no debug location
* Merges string tables from all the entries

As an output, it provides an iterator range that can be used to
serialize the remarks to a file.

Differential Revision: https://reviews.llvm.org/D69141
This commit is contained in:
Francis Visoiu Mistrih 2019-09-10 15:48:55 -07:00
parent 4a79edd8b8
commit 5e640f3b2f
8 changed files with 495 additions and 0 deletions

View File

@ -110,6 +110,21 @@ private:
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(Remark, LLVMRemarkEntryRef)
/// Comparison operators for Remark objects and dependent objects.
template <typename T>
bool operator<(const Optional<T> &LHS, const Optional<T> &RHS) {
// Sorting based on optionals should result in all `None` entries to appear
// before the valid entries. For example, remarks with no debug location will
// appear first.
if (!LHS && !RHS)
return false;
if (!LHS && RHS)
return true;
if (LHS && !RHS)
return false;
return *LHS < *RHS;
}
inline bool operator==(const RemarkLocation &LHS, const RemarkLocation &RHS) {
return LHS.SourceFilePath == RHS.SourceFilePath &&
LHS.SourceLine == RHS.SourceLine &&
@ -120,6 +135,11 @@ inline bool operator!=(const RemarkLocation &LHS, const RemarkLocation &RHS) {
return !(LHS == RHS);
}
inline bool operator<(const RemarkLocation &LHS, const RemarkLocation &RHS) {
return std::make_tuple(LHS.SourceFilePath, LHS.SourceLine, LHS.SourceColumn) <
std::make_tuple(RHS.SourceFilePath, RHS.SourceLine, RHS.SourceColumn);
}
inline bool operator==(const Argument &LHS, const Argument &RHS) {
return LHS.Key == RHS.Key && LHS.Val == RHS.Val && LHS.Loc == RHS.Loc;
}
@ -128,6 +148,11 @@ inline bool operator!=(const Argument &LHS, const Argument &RHS) {
return !(LHS == RHS);
}
inline bool operator<(const Argument &LHS, const Argument &RHS) {
return std::make_tuple(LHS.Key, LHS.Val, LHS.Loc) <
std::make_tuple(RHS.Key, RHS.Val, RHS.Loc);
}
inline bool operator==(const Remark &LHS, const Remark &RHS) {
return LHS.RemarkType == RHS.RemarkType && LHS.PassName == RHS.PassName &&
LHS.RemarkName == RHS.RemarkName &&
@ -139,6 +164,13 @@ inline bool operator!=(const Remark &LHS, const Remark &RHS) {
return !(LHS == RHS);
}
inline bool operator<(const Remark &LHS, const Remark &RHS) {
return std::make_tuple(LHS.RemarkType, LHS.PassName, LHS.RemarkName,
LHS.FunctionName, LHS.Loc, LHS.Hotness, LHS.Args) <
std::make_tuple(RHS.RemarkType, RHS.PassName, RHS.RemarkName,
RHS.FunctionName, RHS.Loc, RHS.Hotness, RHS.Args);
}
} // end namespace remarks
} // end namespace llvm

View File

@ -27,6 +27,9 @@ enum class Format { Unknown, YAML, YAMLStrTab, Bitstream };
/// Parse and validate a string for the remark format.
Expected<Format> parseFormat(StringRef FormatStr);
/// Parse and validate a magic number to a remark format.
Expected<Format> magicToFormat(StringRef Magic);
} // end namespace remarks
} // end namespace llvm

View File

@ -0,0 +1,100 @@
//===-- llvm/Remarks/RemarkLinker.h -----------------------------*- 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 to link together multiple remark files.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_REMARKS_REMARK_LINKER_H
#define LLVM_REMARKS_REMARK_LINKER_H
#include "llvm/Object/ObjectFile.h"
#include "llvm/Remarks/Remark.h"
#include "llvm/Remarks/RemarkFormat.h"
#include "llvm/Remarks/RemarkStringTable.h"
#include "llvm/Support/Error.h"
#include <memory>
#include <set>
namespace llvm {
namespace remarks {
struct RemarkLinker {
private:
/// Compare through the pointers.
struct RemarkPtrCompare {
bool operator()(const std::unique_ptr<Remark> &LHS,
const std::unique_ptr<Remark> &RHS) const {
assert(LHS && RHS && "Invalid pointers to compare.");
return *LHS < *RHS;
};
};
/// The main string table for the remarks.
/// Note: all remarks should use the strings from this string table to avoid
/// dangling references.
StringTable StrTab;
/// A set holding unique remarks.
/// FIXME: std::set is probably not the most appropriate data structure here.
/// Due to the limitation of having a move-only key, there isn't another
/// obvious choice for now.
std::set<std::unique_ptr<Remark>, RemarkPtrCompare> Remarks;
/// A path to append before the external file path found in remark metadata.
Optional<std::string> PrependPath;
/// Keep this remark. If it's already in the set, discard it.
Remark &keep(std::unique_ptr<Remark> Remark);
public:
/// Set a path to prepend to the external file path.
void setExternalFilePrependPath(StringRef PrependPath);
/// Link the remarks found in \p Buffer.
/// If \p RemarkFormat is not provided, try to deduce it from the metadata in
/// \p Buffer.
/// \p Buffer can be either a standalone remark container or just
/// metadata. This takes care of uniquing and merging the remarks.
Error link(StringRef Buffer, Optional<Format> RemarkFormat = None);
/// Link the remarks found in \p Obj by looking for the right section and
/// calling the method above.
Error link(const object::ObjectFile &Obj,
Optional<Format> RemarkFormat = None);
/// Serialize the linked remarks to the stream \p OS, using the format \p
/// RemarkFormat.
/// This clears internal state such as the string table.
/// Note: this implies that the serialization mode is standalone.
Error serialize(raw_ostream &OS, Format RemarksFormat) const;
/// Check whether there are any remarks linked.
bool empty() const { return Remarks.empty(); }
/// Return a collection of the linked unique remarks to iterate on.
/// Ex:
/// for (const Remark &R : RL.remarks() { [...] }
using iterator =
pointee_iterator<std::set<std::unique_ptr<Remark>>::iterator>;
iterator_range<iterator> remarks() const {
return {Remarks.begin(), Remarks.end()};
}
};
/// Returns a buffer with the contents of the remarks section depending on the
/// format of the file. If the section doesn't exist, this returns an empty
/// optional.
Expected<Optional<StringRef>>
getRemarksSectionContents(const object::ObjectFile &Obj);
} // end namespace remarks
} // end namespace llvm
#endif /* LLVM_REMARKS_REMARK_LINKER_H */

View File

@ -3,6 +3,7 @@ add_llvm_library(LLVMRemarks
BitstreamRemarkSerializer.cpp
Remark.cpp
RemarkFormat.cpp
RemarkLinker.cpp
RemarkParser.cpp
RemarkSerializer.cpp
RemarkStringTable.cpp

View File

@ -12,6 +12,7 @@
#include "llvm/Remarks/RemarkFormat.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Remarks/BitstreamRemarkContainer.h"
using namespace llvm;
using namespace llvm::remarks;
@ -30,3 +31,17 @@ Expected<Format> llvm::remarks::parseFormat(StringRef FormatStr) {
return Result;
}
Expected<Format> llvm::remarks::magicToFormat(StringRef Magic) {
auto Result =
StringSwitch<Format>(Magic)
.StartsWith("--- ", Format::YAML) // This is only an assumption.
.StartsWith(remarks::Magic, Format::YAMLStrTab)
.StartsWith(remarks::ContainerMagic, Format::Bitstream)
.Default(Format::Unknown);
if (Result == Format::Unknown)
return createStringError(std::make_error_code(std::errc::invalid_argument),
"Unknown remark magic: '%s'", Magic.data());
return Result;
}

View File

@ -0,0 +1,126 @@
//===- RemarkLinker.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 an implementation of the remark linker.
//
//===----------------------------------------------------------------------===//
#include "llvm/Remarks/RemarkLinker.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Remarks/BitstreamRemarkContainer.h"
#include "llvm/Remarks/RemarkParser.h"
#include "llvm/Remarks/RemarkSerializer.h"
#include "llvm/Support/Error.h"
using namespace llvm;
using namespace llvm::remarks;
static Expected<StringRef>
getRemarksSectionName(const object::ObjectFile &Obj) {
if (Obj.isMachO())
return StringRef("__remarks");
// ELF -> .remarks, but there is no ELF support at this point.
return createStringError(std::errc::illegal_byte_sequence,
"Unsupported file format.");
}
Expected<Optional<StringRef>>
llvm::remarks::getRemarksSectionContents(const object::ObjectFile &Obj) {
Expected<StringRef> SectionName = getRemarksSectionName(Obj);
if (!SectionName)
return SectionName.takeError();
for (const object::SectionRef &Section : Obj.sections()) {
Expected<StringRef> MaybeName = Section.getName();
if (!MaybeName)
return MaybeName.takeError();
if (*MaybeName != *SectionName)
continue;
if (Expected<StringRef> Contents = Section.getContents())
return *Contents;
else
return Contents.takeError();
}
return Optional<StringRef>{};
}
Remark &RemarkLinker::keep(std::unique_ptr<Remark> Remark) {
StrTab.internalize(*Remark);
auto Inserted = Remarks.insert(std::move(Remark));
return **Inserted.first;
}
void RemarkLinker::setExternalFilePrependPath(StringRef PrependPathIn) {
PrependPath = PrependPathIn;
}
// Discard remarks with no source location.
static bool shouldKeepRemark(const Remark &R) { return R.Loc.hasValue(); }
Error RemarkLinker::link(StringRef Buffer, Optional<Format> RemarkFormat) {
if (!RemarkFormat) {
Expected<Format> ParserFormat = magicToFormat(Buffer);
if (!ParserFormat)
return ParserFormat.takeError();
RemarkFormat = *ParserFormat;
}
Expected<std::unique_ptr<RemarkParser>> MaybeParser =
createRemarkParserFromMeta(
*RemarkFormat, Buffer, /*StrTab=*/None,
PrependPath ? Optional<StringRef>(StringRef(*PrependPath))
: Optional<StringRef>(None));
if (!MaybeParser)
return MaybeParser.takeError();
RemarkParser &Parser = **MaybeParser;
while (true) {
Expected<std::unique_ptr<Remark>> Next = Parser.next();
if (Error E = Next.takeError()) {
if (E.isA<EndOfFileError>()) {
consumeError(std::move(E));
break;
}
return E;
}
assert(*Next != nullptr);
if (shouldKeepRemark(**Next))
keep(std::move(*Next));
}
return Error::success();
}
Error RemarkLinker::link(const object::ObjectFile &Obj,
Optional<Format> RemarkFormat) {
Expected<Optional<StringRef>> SectionOrErr = getRemarksSectionContents(Obj);
if (!SectionOrErr)
return SectionOrErr.takeError();
if (Optional<StringRef> Section = *SectionOrErr)
return link(*Section, RemarkFormat);
return Error::success();
}
Error RemarkLinker::serialize(raw_ostream &OS, Format RemarksFormat) const {
Expected<std::unique_ptr<RemarkSerializer>> MaybeSerializer =
createRemarkSerializer(RemarksFormat, SerializerMode::Standalone, OS,
std::move(const_cast<StringTable &>(StrTab)));
if (!MaybeSerializer)
return MaybeSerializer.takeError();
std::unique_ptr<remarks::RemarkSerializer> Serializer =
std::move(*MaybeSerializer);
for (const Remark &R : remarks())
Serializer->emit(R);
return Error::success();
}

View File

@ -9,6 +9,7 @@ add_llvm_unittest(RemarksTests
BitstreamRemarksParsingTest.cpp
BitstreamRemarksSerializerTest.cpp
RemarksAPITest.cpp
RemarksLinkingTest.cpp
RemarksStrTabParsingTest.cpp
YAMLRemarksParsingTest.cpp
YAMLRemarksSerializerTest.cpp

View File

@ -0,0 +1,217 @@
//===- unittest/Support/RemarksLinkingTest.cpp - Linking 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/Bitcode/BitcodeAnalyzer.h"
#include "llvm/Remarks/RemarkLinker.h"
#include "llvm/Remarks/RemarkSerializer.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
#include <string>
using namespace llvm;
static void serializeAndCheck(remarks::RemarkLinker &RL,
remarks::Format OutputFormat,
StringRef ExpectedOutput) {
// 1. Create a serializer.
// 2. Serialize all the remarks from the linker.
// 3. Check that it matches the output.
std::string Buf;
raw_string_ostream OS(Buf);
Error E = RL.serialize(OS, OutputFormat);
EXPECT_FALSE(static_cast<bool>(E));
// For bitstream, run it through the analyzer.
if (OutputFormat == remarks::Format::Bitstream) {
std::string AnalyzeBuf;
raw_string_ostream AnalyzeOS(AnalyzeBuf);
BCDumpOptions O(AnalyzeOS);
O.ShowBinaryBlobs = true;
BitcodeAnalyzer BA(OS.str());
EXPECT_FALSE(BA.analyze(O)); // Expect no errors.
EXPECT_EQ(AnalyzeOS.str(), ExpectedOutput);
} else {
EXPECT_EQ(OS.str(), ExpectedOutput);
}
}
static void check(remarks::Format InputFormat, StringRef Input,
remarks::Format OutputFormat, StringRef ExpectedOutput) {
remarks::RemarkLinker RL;
EXPECT_FALSE(RL.link(Input, InputFormat));
serializeAndCheck(RL, OutputFormat, ExpectedOutput);
}
static void check(remarks::Format InputFormat, StringRef Input,
remarks::Format InputFormat2, StringRef Input2,
remarks::Format OutputFormat, StringRef ExpectedOutput) {
remarks::RemarkLinker RL;
EXPECT_FALSE(RL.link(Input, InputFormat));
EXPECT_FALSE(RL.link(Input2, InputFormat2));
serializeAndCheck(RL, OutputFormat, ExpectedOutput);
}
TEST(Remarks, LinkingGoodYAML) {
// One YAML remark.
check(remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n",
remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n");
// Check that we don't keep remarks without debug locations.
check(remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"Function: foo\n"
"...\n",
remarks::Format::YAML, "");
// Check that we deduplicate remarks.
check(remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n"
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n",
remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n");
}
TEST(Remarks, LinkingGoodBitstream) {
// One YAML remark.
check(remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n",
remarks::Format::Bitstream,
"<BLOCKINFO_BLOCK/>\n"
"<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n"
" <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n"
" <Remark version codeid=2 abbrevid=5 op0=0/>\n"
" <String table codeid=3 abbrevid=6/> blob data = "
"'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n"
"</Meta>\n"
"<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
" <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n"
" <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n"
"</Remark>\n");
// Check that we deduplicate remarks.
check(remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n"
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n",
remarks::Format::Bitstream,
"<BLOCKINFO_BLOCK/>\n"
"<Meta BlockID=8 NumWords=12 BlockCodeSize=3>\n"
" <Container info codeid=1 abbrevid=4 op0=0 op1=2/>\n"
" <Remark version codeid=2 abbrevid=5 op0=0/>\n"
" <String table codeid=3 abbrevid=6/> blob data = "
"'inline\\x00NoDefinition\\x00foo\\x00file.c\\x00'\n"
"</Meta>\n"
"<Remark BlockID=9 NumWords=4 BlockCodeSize=4>\n"
" <Remark header codeid=5 abbrevid=4 op0=2 op1=1 op2=0 op3=2/>\n"
" <Remark debug location codeid=6 abbrevid=5 op0=3 op1=3 op2=12/>\n"
"</Remark>\n");
}
TEST(Remarks, LinkingGoodStrTab) {
// Check that remarks from different entries use the same strtab.
check(remarks::Format::YAML,
"--- !Missed\n"
"Pass: inline\n"
"Name: NoDefinition\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n",
remarks::Format::YAML,
"--- !Passed\n"
"Pass: inline\n"
"Name: Ok\n"
"DebugLoc: { File: file.c, Line: 3, Column: 12 }\n"
"Function: foo\n"
"...\n",
remarks::Format::YAMLStrTab,
StringRef("REMARKS\0\0\0\0\0\0\0\0\0\x22\0\0\0\0\0\0\0"
"inline\0NoDefinition\0foo\0file.c\0Ok\0"
"--- !Passed\n"
"Pass: 0\n"
"Name: 4\n"
"DebugLoc: { File: 3, Line: 3, Column: 12 }\n"
"Function: 2\n"
"...\n"
"--- !Missed\n"
"Pass: 0\n"
"Name: 1\n"
"DebugLoc: { File: 3, Line: 3, Column: 12 }\n"
"Function: 2\n"
"...\n",
304));
}
// Check that we propagate parsing errors.
TEST(Remarks, LinkingError) {
remarks::RemarkLinker RL;
{
Error E = RL.link("badyaml", remarks::Format::YAML);
EXPECT_TRUE(static_cast<bool>(E));
EXPECT_EQ(toString(std::move(E)),
"YAML:1:1: error: document root is not of mapping type.\n"
"\n"
"badyaml\n"
"^~~~~~~\n"
"\n");
}
{
// Check that the prepend path is propagated and fails with the full path.
RL.setExternalFilePrependPath("/baddir/");
Error E = RL.link(
StringRef("REMARKS\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0badfile.opt.yaml",
40),
remarks::Format::YAMLStrTab);
EXPECT_TRUE(static_cast<bool>(E));
EXPECT_EQ(toString(std::move(E)),
"'/baddir/badfile.opt.yaml': No such file or directory");
}
}