diff --git a/llvm/include/llvm/ProfileData/PGOCtxProfReader.h b/llvm/include/llvm/ProfileData/PGOCtxProfReader.h new file mode 100644 index 00000000000000..a19b3f51d642de --- /dev/null +++ b/llvm/include/llvm/ProfileData/PGOCtxProfReader.h @@ -0,0 +1,92 @@ +//===--- PGOCtxProfReader.h - Contextual profile reader ---------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// +/// Reader for contextual iFDO profile, which comes in bitstream format. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H +#define LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H + +#include "llvm/ADT/DenseSet.h" +#include "llvm/Bitstream/BitstreamReader.h" +#include "llvm/IR/GlobalValue.h" +#include "llvm/ProfileData/PGOCtxProfWriter.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace llvm { +/// The loaded contextual profile, suitable for mutation during IPO passes. We +/// generally expect a fraction of counters and of callsites to be populated. +/// We continue to model counters as vectors, but callsites are modeled as a map +/// of a map. The expectation is that, typically, there is a small number of +/// indirect targets (usually, 1 for direct calls); but potentially a large +/// number of callsites, and, as inlining progresses, the callsite count of a +/// caller will grow. +class PGOContextualProfile final { +public: + using CallTargetMapTy = std::map; + using CallsiteMapTy = DenseMap; + +private: + friend class PGOCtxProfileReader; + GlobalValue::GUID GUID = 0; + SmallVector Counters; + CallsiteMapTy Callsites; + + PGOContextualProfile(GlobalValue::GUID G, + SmallVectorImpl &&Counters) + : GUID(G), Counters(std::move(Counters)) {} + + Expected + getOrEmplace(uint32_t Index, GlobalValue::GUID G, + SmallVectorImpl &&Counters); + +public: + PGOContextualProfile(const PGOContextualProfile &) = delete; + PGOContextualProfile &operator=(const PGOContextualProfile &) = delete; + PGOContextualProfile(PGOContextualProfile &&) = default; + PGOContextualProfile &operator=(PGOContextualProfile &&) = default; + + GlobalValue::GUID guid() const { return GUID; } + const SmallVectorImpl &counters() const { return Counters; } + const CallsiteMapTy &callsites() const { return Callsites; } + CallsiteMapTy &callsites() { return Callsites; } + + bool hasCallsite(uint32_t I) const { + return Callsites.find(I) != Callsites.end(); + } + + const CallTargetMapTy &callsite(uint32_t I) const { + assert(hasCallsite(I) && "Callsite not found"); + return Callsites.find(I)->second; + } + void getContainedGuids(DenseSet &Guids) const; +}; + +class PGOCtxProfileReader final { + BitstreamCursor &Cursor; + Expected advance(); + Error readMetadata(); + Error wrongValue(const Twine &); + Error unsupported(const Twine &); + + Expected, PGOContextualProfile>> + readContext(bool ExpectIndex); + bool canReadContext(); + +public: + PGOCtxProfileReader(BitstreamCursor &Cursor) : Cursor(Cursor) {} + + Expected> loadContexts(); +}; +} // namespace llvm +#endif diff --git a/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h b/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h new file mode 100644 index 00000000000000..15578c51a49578 --- /dev/null +++ b/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h @@ -0,0 +1,91 @@ +//===- PGOCtxProfWriter.h - Contextual Profile Writer -----------*- 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 declares a utility for writing a contextual profile to bitstream. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_PROFILEDATA_PGOCTXPROFWRITER_H_ +#define LLVM_PROFILEDATA_PGOCTXPROFWRITER_H_ + +#include "llvm/Bitstream/BitstreamWriter.h" +#include "llvm/ProfileData/CtxInstrContextNode.h" + +namespace llvm { +enum PGOCtxProfileRecords { Invalid = 0, Version, Guid, CalleeIndex, Counters }; + +enum PGOCtxProfileBlockIDs { + ProfileMetadataBlockID = 100, + ContextNodeBlockID = ProfileMetadataBlockID + 1 +}; + +/// Write one or more ContextNodes to the provided raw_fd_stream. +/// The caller must destroy the PGOCtxProfileWriter object before closing the +/// stream. +/// The design allows serializing a bunch of contexts embedded in some other +/// file. The overall format is: +/// +/// [... other data written to the stream...] +/// SubBlock(ProfileMetadataBlockID) +/// Version +/// SubBlock(ContextNodeBlockID) +/// [RECORDS] +/// SubBlock(ContextNodeBlockID) +/// [RECORDS] +/// [... more SubBlocks] +/// EndBlock +/// EndBlock +/// +/// The "RECORDS" are bitsream records. The IDs are in CtxProfileCodes (except) +/// for Version, which is just for metadata). All contexts will have Guid and +/// Counters, and all but the roots have CalleeIndex. The order in which the +/// records appear does not matter, but they must precede any subcontexts, +/// because that helps keep the reader code simpler. +/// +/// Subblock containment captures the context->subcontext relationship. The +/// "next()" relationship in the raw profile, between call targets of indirect +/// calls, are just modeled as peer subblocks where the callee index is the +/// same. +/// +/// Versioning: the writer may produce additional records not known by the +/// reader. The version number indicates a more structural change. +/// The current version, in particular, is set up to expect optional extensions +/// like value profiling - which would appear as additional records. For +/// example, value profiling would produce a new record with a new record ID, +/// containing the profiled values (much like the counters) +class PGOCtxProfileWriter final { + SmallVector Buff; + BitstreamWriter Writer; + + void writeCounters(const ctx_profile::ContextNode &Node); + void writeImpl(std::optional CallerIndex, + const ctx_profile::ContextNode &Node); + +public: + PGOCtxProfileWriter(raw_fd_stream &Out, + std::optional VersionOverride = std::nullopt) + : Writer(Buff, &Out, 0) { + Writer.EnterSubblock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID, + CodeLen); + const auto Version = VersionOverride ? *VersionOverride : CurrentVersion; + Writer.EmitRecord(PGOCtxProfileRecords::Version, + SmallVector({Version})); + } + + ~PGOCtxProfileWriter() { Writer.ExitBlock(); } + + void write(const ctx_profile::ContextNode &); + + // constants used in writing which a reader may find useful. + static constexpr unsigned CodeLen = 2; + static constexpr uint32_t CurrentVersion = 1; + static constexpr unsigned VBREncodingBits = 6; +}; + +} // namespace llvm +#endif diff --git a/llvm/lib/ProfileData/CMakeLists.txt b/llvm/lib/ProfileData/CMakeLists.txt index 408f9ff01ec87d..2397eebaf7b19d 100644 --- a/llvm/lib/ProfileData/CMakeLists.txt +++ b/llvm/lib/ProfileData/CMakeLists.txt @@ -7,6 +7,8 @@ add_llvm_component_library(LLVMProfileData ItaniumManglingCanonicalizer.cpp MemProf.cpp MemProfReader.cpp + PGOCtxProfReader.cpp + PGOCtxProfWriter.cpp ProfileSummaryBuilder.cpp SampleProf.cpp SampleProfReader.cpp diff --git a/llvm/lib/ProfileData/PGOCtxProfReader.cpp b/llvm/lib/ProfileData/PGOCtxProfReader.cpp new file mode 100644 index 00000000000000..3710f2e4b81858 --- /dev/null +++ b/llvm/lib/ProfileData/PGOCtxProfReader.cpp @@ -0,0 +1,173 @@ +//===- PGOCtxProfReader.cpp - Contextual Instrumentation profile reader ---===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Read a contextual profile into a datastructure suitable for maintenance +// throughout IPO +// +//===----------------------------------------------------------------------===// + +#include "llvm/ProfileData/PGOCtxProfReader.h" +#include "llvm/Bitstream/BitCodeEnums.h" +#include "llvm/Bitstream/BitstreamReader.h" +#include "llvm/ProfileData/InstrProf.h" +#include "llvm/ProfileData/PGOCtxProfWriter.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" + +using namespace llvm; + +// FIXME(#92054) - these Error handling macros are (re-)invented in a few +// places. +#define EXPECT_OR_RET(LHS, RHS) \ + auto LHS = RHS; \ + if (!LHS) \ + return LHS.takeError(); + +#define RET_ON_ERR(EXPR) \ + if (auto Err = (EXPR)) \ + return Err; + +Expected +PGOContextualProfile::getOrEmplace(uint32_t Index, GlobalValue::GUID G, + SmallVectorImpl &&Counters) { + auto [Iter, Inserted] = Callsites[Index].insert( + {G, PGOContextualProfile(G, std::move(Counters))}); + if (!Inserted) + return make_error(instrprof_error::invalid_prof, + "Duplicate GUID for same callsite."); + return Iter->second; +} + +void PGOContextualProfile::getContainedGuids( + DenseSet &Guids) const { + Guids.insert(GUID); + for (const auto &[_, Callsite] : Callsites) + for (const auto &[_, Callee] : Callsite) + Callee.getContainedGuids(Guids); +} + +Expected PGOCtxProfileReader::advance() { + return Cursor.advance(BitstreamCursor::AF_DontAutoprocessAbbrevs); +} + +Error PGOCtxProfileReader::wrongValue(const Twine &Msg) { + return make_error(instrprof_error::invalid_prof, Msg); +} + +Error PGOCtxProfileReader::unsupported(const Twine &Msg) { + return make_error(instrprof_error::unsupported_version, Msg); +} + +bool PGOCtxProfileReader::canReadContext() { + auto Blk = advance(); + if (!Blk) { + consumeError(Blk.takeError()); + return false; + } + return Blk->Kind == BitstreamEntry::SubBlock && + Blk->ID == PGOCtxProfileBlockIDs::ContextNodeBlockID; +} + +Expected, PGOContextualProfile>> +PGOCtxProfileReader::readContext(bool ExpectIndex) { + RET_ON_ERR(Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ContextNodeBlockID)); + + std::optional Guid; + std::optional> Counters; + std::optional CallsiteIndex; + + SmallVector RecordValues; + + // We don't prescribe the order in which the records come in, and we are ok + // if other unsupported records appear. We seek in the current subblock until + // we get all we know. + auto GotAllWeNeed = [&]() { + return Guid.has_value() && Counters.has_value() && + (!ExpectIndex || CallsiteIndex.has_value()); + }; + while (!GotAllWeNeed()) { + RecordValues.clear(); + EXPECT_OR_RET(Entry, advance()); + if (Entry->Kind != BitstreamEntry::Record) + return wrongValue( + "Expected records before encountering more subcontexts"); + EXPECT_OR_RET(ReadRecord, + Cursor.readRecord(bitc::UNABBREV_RECORD, RecordValues)); + switch (*ReadRecord) { + case PGOCtxProfileRecords::Guid: + if (RecordValues.size() != 1) + return wrongValue("The GUID record should have exactly one value"); + Guid = RecordValues[0]; + break; + case PGOCtxProfileRecords::Counters: + Counters = std::move(RecordValues); + if (Counters->empty()) + return wrongValue("Empty counters. At least the entry counter (one " + "value) was expected"); + break; + case PGOCtxProfileRecords::CalleeIndex: + if (!ExpectIndex) + return wrongValue("The root context should not have a callee index"); + if (RecordValues.size() != 1) + return wrongValue("The callee index should have exactly one value"); + CallsiteIndex = RecordValues[0]; + break; + default: + // OK if we see records we do not understand, like records (profile + // components) introduced later. + break; + } + } + + PGOContextualProfile Ret(*Guid, std::move(*Counters)); + + while (canReadContext()) { + EXPECT_OR_RET(SC, readContext(true)); + auto &Targets = Ret.callsites()[*SC->first]; + auto [_, Inserted] = + Targets.insert({SC->second.guid(), std::move(SC->second)}); + if (!Inserted) + return wrongValue( + "Unexpected duplicate target (callee) at the same callsite."); + } + return std::make_pair(CallsiteIndex, std::move(Ret)); +} + +Error PGOCtxProfileReader::readMetadata() { + EXPECT_OR_RET(Blk, advance()); + if (Blk->Kind != BitstreamEntry::SubBlock) + return unsupported("Expected Version record"); + RET_ON_ERR( + Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID)); + EXPECT_OR_RET(MData, advance()); + if (MData->Kind != BitstreamEntry::Record) + return unsupported("Expected Version record"); + + SmallVector Ver; + EXPECT_OR_RET(Code, Cursor.readRecord(bitc::UNABBREV_RECORD, Ver)); + if (*Code != PGOCtxProfileRecords::Version) + return unsupported("Expected Version record"); + if (Ver.size() != 1 || Ver[0] > PGOCtxProfileWriter::CurrentVersion) + return unsupported("Version " + Twine(*Code) + + " is higher than supported version " + + Twine(PGOCtxProfileWriter::CurrentVersion)); + return Error::success(); +} + +Expected> +PGOCtxProfileReader::loadContexts() { + std::map Ret; + RET_ON_ERR(readMetadata()); + while (canReadContext()) { + EXPECT_OR_RET(E, readContext(false)); + auto Key = E->second.guid(); + if (!Ret.insert({Key, std::move(E->second)}).second) + return wrongValue("Duplicate roots"); + } + return Ret; +} diff --git a/llvm/lib/ProfileData/PGOCtxProfWriter.cpp b/llvm/lib/ProfileData/PGOCtxProfWriter.cpp new file mode 100644 index 00000000000000..50817975644693 --- /dev/null +++ b/llvm/lib/ProfileData/PGOCtxProfWriter.cpp @@ -0,0 +1,49 @@ +//===- PGOCtxProfWriter.cpp - Contextual Instrumentation profile writer ---===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Write a contextual profile to bitstream. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ProfileData/PGOCtxProfWriter.h" +#include "llvm/Bitstream/BitCodeEnums.h" + +using namespace llvm; +using namespace llvm::ctx_profile; + +void PGOCtxProfileWriter::writeCounters(const ContextNode &Node) { + Writer.EmitCode(bitc::UNABBREV_RECORD); + Writer.EmitVBR(PGOCtxProfileRecords::Counters, VBREncodingBits); + Writer.EmitVBR(Node.counters_size(), VBREncodingBits); + for (uint32_t I = 0U; I < Node.counters_size(); ++I) + Writer.EmitVBR64(Node.counters()[I], VBREncodingBits); +} + +// recursively write all the subcontexts. We do need to traverse depth first to +// model the context->subcontext implicitly, and since this captures call +// stacks, we don't really need to be worried about stack overflow and we can +// keep the implementation simple. +void PGOCtxProfileWriter::writeImpl(std::optional CallerIndex, + const ContextNode &Node) { + Writer.EnterSubblock(PGOCtxProfileBlockIDs::ContextNodeBlockID, CodeLen); + Writer.EmitRecord(PGOCtxProfileRecords::Guid, + SmallVector{Node.guid()}); + if (CallerIndex) + Writer.EmitRecord(PGOCtxProfileRecords::CalleeIndex, + SmallVector{*CallerIndex}); + writeCounters(Node); + for (uint32_t I = 0U; I < Node.callsites_size(); ++I) + for (const auto *Subcontext = Node.subContexts()[I]; Subcontext; + Subcontext = Subcontext->next()) + writeImpl(I, *Subcontext); + Writer.ExitBlock(); +} + +void PGOCtxProfileWriter::write(const ContextNode &RootNode) { + writeImpl(std::nullopt, RootNode); +} diff --git a/llvm/unittests/ProfileData/CMakeLists.txt b/llvm/unittests/ProfileData/CMakeLists.txt index ce3a0a45ccf18c..c92642ded82820 100644 --- a/llvm/unittests/ProfileData/CMakeLists.txt +++ b/llvm/unittests/ProfileData/CMakeLists.txt @@ -13,6 +13,7 @@ add_llvm_unittest(ProfileDataTests InstrProfTest.cpp ItaniumManglingCanonicalizerTest.cpp MemProfTest.cpp + PGOCtxProfReaderWriterTest.cpp SampleProfTest.cpp SymbolRemappingReaderTest.cpp ) diff --git a/llvm/unittests/ProfileData/PGOCtxProfReaderWriterTest.cpp b/llvm/unittests/ProfileData/PGOCtxProfReaderWriterTest.cpp new file mode 100644 index 00000000000000..d2cdbb28e2fce7 --- /dev/null +++ b/llvm/unittests/ProfileData/PGOCtxProfReaderWriterTest.cpp @@ -0,0 +1,255 @@ +//===-------------- PGOCtxProfReadWriteTest.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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/Bitstream/BitstreamReader.h" +#include "llvm/ProfileData/CtxInstrContextNode.h" +#include "llvm/ProfileData/PGOCtxProfReader.h" +#include "llvm/ProfileData/PGOCtxProfWriter.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/SupportHelpers.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::ctx_profile; + +class PGOCtxProfRWTest : public ::testing::Test { + std::vector> Nodes; + std::map Roots; + +public: + ContextNode *createNode(GUID Guid, uint32_t NrCounters, uint32_t NrCallsites, + ContextNode *Next = nullptr) { + auto AllocSize = ContextNode::getAllocSize(NrCounters, NrCallsites); + auto *Mem = Nodes.emplace_back(std::make_unique(AllocSize)).get(); + std::memset(Mem, 0, AllocSize); + auto *Ret = new (Mem) ContextNode(Guid, NrCounters, NrCallsites, Next); + return Ret; + } + + void SetUp() override { + // Root (guid 1) has 2 callsites, one used for an indirect call to either + // guid 2 or 4. + // guid 2 calls guid 5 + // guid 5 calls guid 2 + // there's also a second root, guid3. + auto *Root1 = createNode(1, 2, 2); + Root1->counters()[0] = 10; + Root1->counters()[1] = 11; + Roots.insert({1, Root1}); + auto *L1 = createNode(2, 1, 1); + L1->counters()[0] = 12; + Root1->subContexts()[1] = createNode(4, 3, 1, L1); + Root1->subContexts()[1]->counters()[0] = 13; + Root1->subContexts()[1]->counters()[1] = 14; + Root1->subContexts()[1]->counters()[2] = 15; + + auto *L3 = createNode(5, 6, 3); + for (auto I = 0; I < 6; ++I) + L3->counters()[I] = 16 + I; + L1->subContexts()[0] = L3; + L3->subContexts()[2] = createNode(2, 1, 1); + L3->subContexts()[2]->counters()[0] = 30; + auto *Root2 = createNode(3, 1, 0); + Root2->counters()[0] = 40; + Roots.insert({3, Root2}); + } + + const std::map &roots() const { return Roots; } +}; + +void checkSame(const ContextNode &Raw, const PGOContextualProfile &Profile) { + EXPECT_EQ(Raw.guid(), Profile.guid()); + ASSERT_EQ(Raw.counters_size(), Profile.counters().size()); + for (auto I = 0U; I < Raw.counters_size(); ++I) + EXPECT_EQ(Raw.counters()[I], Profile.counters()[I]); + + for (auto I = 0U; I < Raw.callsites_size(); ++I) { + if (Raw.subContexts()[I] == nullptr) + continue; + EXPECT_TRUE(Profile.hasCallsite(I)); + const auto &ProfileTargets = Profile.callsite(I); + + std::map Targets; + for (const auto *N = Raw.subContexts()[I]; N; N = N->next()) + EXPECT_TRUE(Targets.insert({N->guid(), N}).second); + + EXPECT_EQ(Targets.size(), ProfileTargets.size()); + for (auto It : Targets) { + auto PIt = ProfileTargets.find(It.second->guid()); + EXPECT_NE(PIt, ProfileTargets.end()); + checkSame(*It.second, PIt->second); + } + } +} + +TEST_F(PGOCtxProfRWTest, RoundTrip) { + llvm::unittest::TempFile ProfileFile("ctx_profile", "", "", /*Unique*/ true); + { + std::error_code EC; + raw_fd_stream Out(ProfileFile.path(), EC); + ASSERT_FALSE(EC); + { + PGOCtxProfileWriter Writer(Out); + for (auto &[_, R] : roots()) + Writer.write(*R); + } + } + { + ErrorOr> MB = + MemoryBuffer::getFile(ProfileFile.path()); + ASSERT_TRUE(!!MB); + ASSERT_NE(*MB, nullptr); + BitstreamCursor Cursor((*MB)->getBuffer()); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + ASSERT_TRUE(!!Expected); + auto &Ctxes = *Expected; + EXPECT_EQ(Ctxes.size(), roots().size()); + EXPECT_EQ(Ctxes.size(), 2U); + for (auto &[G, R] : roots()) + checkSame(*R, Ctxes.find(G)->second); + } +} + +TEST_F(PGOCtxProfRWTest, InvalidCounters) { + auto *R = createNode(1, 0, 1); + llvm::unittest::TempFile ProfileFile("ctx_profile", "", "", /*Unique*/ true); + { + std::error_code EC; + raw_fd_stream Out(ProfileFile.path(), EC); + ASSERT_FALSE(EC); + { + PGOCtxProfileWriter Writer(Out); + Writer.write(*R); + } + } + { + auto MB = MemoryBuffer::getFile(ProfileFile.path()); + ASSERT_TRUE(!!MB); + ASSERT_NE(*MB, nullptr); + BitstreamCursor Cursor((*MB)->getBuffer()); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + EXPECT_FALSE(Expected); + consumeError(Expected.takeError()); + } +} + +TEST_F(PGOCtxProfRWTest, Empty) { + BitstreamCursor Cursor(""); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + EXPECT_FALSE(Expected); + consumeError(Expected.takeError()); +} + +TEST_F(PGOCtxProfRWTest, Invalid) { + BitstreamCursor Cursor("Surely this is not valid"); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + EXPECT_FALSE(Expected); + consumeError(Expected.takeError()); +} + +TEST_F(PGOCtxProfRWTest, ValidButEmpty) { + llvm::unittest::TempFile ProfileFile("ctx_profile", "", "", /*Unique*/ true); + { + std::error_code EC; + raw_fd_stream Out(ProfileFile.path(), EC); + ASSERT_FALSE(EC); + { + PGOCtxProfileWriter Writer(Out); + // don't write anything - this will just produce the metadata subblock. + } + } + { + auto MB = MemoryBuffer::getFile(ProfileFile.path()); + ASSERT_TRUE(!!MB); + ASSERT_NE(*MB, nullptr); + BitstreamCursor Cursor((*MB)->getBuffer()); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + EXPECT_TRUE(!!Expected); + EXPECT_TRUE(Expected->empty()); + } +} + +TEST_F(PGOCtxProfRWTest, WrongVersion) { + llvm::unittest::TempFile ProfileFile("ctx_profile", "", "", /*Unique*/ true); + { + std::error_code EC; + raw_fd_stream Out(ProfileFile.path(), EC); + ASSERT_FALSE(EC); + { + PGOCtxProfileWriter Writer(Out, PGOCtxProfileWriter::CurrentVersion + 1); + } + } + { + auto MB = MemoryBuffer::getFile(ProfileFile.path()); + ASSERT_TRUE(!!MB); + ASSERT_NE(*MB, nullptr); + BitstreamCursor Cursor((*MB)->getBuffer()); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + EXPECT_FALSE(Expected); + consumeError(Expected.takeError()); + } +} + +TEST_F(PGOCtxProfRWTest, DuplicateRoots) { + llvm::unittest::TempFile ProfileFile("ctx_profile", "", "", /*Unique*/ true); + { + std::error_code EC; + raw_fd_stream Out(ProfileFile.path(), EC); + ASSERT_FALSE(EC); + { + PGOCtxProfileWriter Writer(Out); + Writer.write(*createNode(1, 1, 1)); + Writer.write(*createNode(1, 1, 1)); + } + } + { + auto MB = MemoryBuffer::getFile(ProfileFile.path()); + ASSERT_TRUE(!!MB); + ASSERT_NE(*MB, nullptr); + BitstreamCursor Cursor((*MB)->getBuffer()); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + EXPECT_FALSE(Expected); + consumeError(Expected.takeError()); + } +} + +TEST_F(PGOCtxProfRWTest, DuplicateTargets) { + llvm::unittest::TempFile ProfileFile("ctx_profile", "", "", /*Unique*/ true); + { + std::error_code EC; + raw_fd_stream Out(ProfileFile.path(), EC); + ASSERT_FALSE(EC); + { + auto *R = createNode(1, 1, 1); + auto *L1 = createNode(2, 1, 0); + auto *L2 = createNode(2, 1, 0, L1); + R->subContexts()[0] = L2; + PGOCtxProfileWriter Writer(Out); + Writer.write(*R); + } + } + { + auto MB = MemoryBuffer::getFile(ProfileFile.path()); + ASSERT_TRUE(!!MB); + ASSERT_NE(*MB, nullptr); + BitstreamCursor Cursor((*MB)->getBuffer()); + PGOCtxProfileReader Reader(Cursor); + auto Expected = Reader.loadContexts(); + EXPECT_FALSE(Expected); + consumeError(Expected.takeError()); + } +}