Skip to content

Commit c19f2c7

Browse files
committed
Reapply "[ctx_profile] Profile reader and writer" (#92199)
This reverts commit 03c7458. One of the problems was addressed in #92208 The other problem: needed to add `BitstreamReader` to the list of link deps of `LLVMProfileData`
1 parent ec36145 commit c19f2c7

File tree

7 files changed

+664
-0
lines changed

7 files changed

+664
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//===--- PGOCtxProfReader.h - Contextual profile reader ---------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
///
11+
/// Reader for contextual iFDO profile, which comes in bitstream format.
12+
///
13+
//===----------------------------------------------------------------------===//
14+
15+
#ifndef LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H
16+
#define LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H
17+
18+
#include "llvm/ADT/DenseSet.h"
19+
#include "llvm/Bitstream/BitstreamReader.h"
20+
#include "llvm/IR/GlobalValue.h"
21+
#include "llvm/ProfileData/PGOCtxProfWriter.h"
22+
#include "llvm/Support/Error.h"
23+
#include <map>
24+
#include <vector>
25+
26+
namespace llvm {
27+
/// The loaded contextual profile, suitable for mutation during IPO passes. We
28+
/// generally expect a fraction of counters and of callsites to be populated.
29+
/// We continue to model counters as vectors, but callsites are modeled as a map
30+
/// of a map. The expectation is that, typically, there is a small number of
31+
/// indirect targets (usually, 1 for direct calls); but potentially a large
32+
/// number of callsites, and, as inlining progresses, the callsite count of a
33+
/// caller will grow.
34+
class PGOContextualProfile final {
35+
public:
36+
using CallTargetMapTy = std::map<GlobalValue::GUID, PGOContextualProfile>;
37+
using CallsiteMapTy = DenseMap<uint32_t, CallTargetMapTy>;
38+
39+
private:
40+
friend class PGOCtxProfileReader;
41+
GlobalValue::GUID GUID = 0;
42+
SmallVector<uint64_t, 16> Counters;
43+
CallsiteMapTy Callsites;
44+
45+
PGOContextualProfile(GlobalValue::GUID G,
46+
SmallVectorImpl<uint64_t> &&Counters)
47+
: GUID(G), Counters(std::move(Counters)) {}
48+
49+
Expected<PGOContextualProfile &>
50+
getOrEmplace(uint32_t Index, GlobalValue::GUID G,
51+
SmallVectorImpl<uint64_t> &&Counters);
52+
53+
public:
54+
PGOContextualProfile(const PGOContextualProfile &) = delete;
55+
PGOContextualProfile &operator=(const PGOContextualProfile &) = delete;
56+
PGOContextualProfile(PGOContextualProfile &&) = default;
57+
PGOContextualProfile &operator=(PGOContextualProfile &&) = default;
58+
59+
GlobalValue::GUID guid() const { return GUID; }
60+
const SmallVectorImpl<uint64_t> &counters() const { return Counters; }
61+
const CallsiteMapTy &callsites() const { return Callsites; }
62+
CallsiteMapTy &callsites() { return Callsites; }
63+
64+
bool hasCallsite(uint32_t I) const {
65+
return Callsites.find(I) != Callsites.end();
66+
}
67+
68+
const CallTargetMapTy &callsite(uint32_t I) const {
69+
assert(hasCallsite(I) && "Callsite not found");
70+
return Callsites.find(I)->second;
71+
}
72+
void getContainedGuids(DenseSet<GlobalValue::GUID> &Guids) const;
73+
};
74+
75+
class PGOCtxProfileReader final {
76+
BitstreamCursor &Cursor;
77+
Expected<BitstreamEntry> advance();
78+
Error readMetadata();
79+
Error wrongValue(const Twine &);
80+
Error unsupported(const Twine &);
81+
82+
Expected<std::pair<std::optional<uint32_t>, PGOContextualProfile>>
83+
readContext(bool ExpectIndex);
84+
bool canReadContext();
85+
86+
public:
87+
PGOCtxProfileReader(BitstreamCursor &Cursor) : Cursor(Cursor) {}
88+
89+
Expected<std::map<GlobalValue::GUID, PGOContextualProfile>> loadContexts();
90+
};
91+
} // namespace llvm
92+
#endif
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//===- PGOCtxProfWriter.h - Contextual Profile Writer -----------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file declares a utility for writing a contextual profile to bitstream.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef LLVM_PROFILEDATA_PGOCTXPROFWRITER_H_
14+
#define LLVM_PROFILEDATA_PGOCTXPROFWRITER_H_
15+
16+
#include "llvm/Bitstream/BitstreamWriter.h"
17+
#include "llvm/ProfileData/CtxInstrContextNode.h"
18+
19+
namespace llvm {
20+
enum PGOCtxProfileRecords { Invalid = 0, Version, Guid, CalleeIndex, Counters };
21+
22+
enum PGOCtxProfileBlockIDs {
23+
ProfileMetadataBlockID = 100,
24+
ContextNodeBlockID = ProfileMetadataBlockID + 1
25+
};
26+
27+
/// Write one or more ContextNodes to the provided raw_fd_stream.
28+
/// The caller must destroy the PGOCtxProfileWriter object before closing the
29+
/// stream.
30+
/// The design allows serializing a bunch of contexts embedded in some other
31+
/// file. The overall format is:
32+
///
33+
/// [... other data written to the stream...]
34+
/// SubBlock(ProfileMetadataBlockID)
35+
/// Version
36+
/// SubBlock(ContextNodeBlockID)
37+
/// [RECORDS]
38+
/// SubBlock(ContextNodeBlockID)
39+
/// [RECORDS]
40+
/// [... more SubBlocks]
41+
/// EndBlock
42+
/// EndBlock
43+
///
44+
/// The "RECORDS" are bitsream records. The IDs are in CtxProfileCodes (except)
45+
/// for Version, which is just for metadata). All contexts will have Guid and
46+
/// Counters, and all but the roots have CalleeIndex. The order in which the
47+
/// records appear does not matter, but they must precede any subcontexts,
48+
/// because that helps keep the reader code simpler.
49+
///
50+
/// Subblock containment captures the context->subcontext relationship. The
51+
/// "next()" relationship in the raw profile, between call targets of indirect
52+
/// calls, are just modeled as peer subblocks where the callee index is the
53+
/// same.
54+
///
55+
/// Versioning: the writer may produce additional records not known by the
56+
/// reader. The version number indicates a more structural change.
57+
/// The current version, in particular, is set up to expect optional extensions
58+
/// like value profiling - which would appear as additional records. For
59+
/// example, value profiling would produce a new record with a new record ID,
60+
/// containing the profiled values (much like the counters)
61+
class PGOCtxProfileWriter final {
62+
SmallVector<char, 1 << 20> Buff;
63+
BitstreamWriter Writer;
64+
65+
void writeCounters(const ctx_profile::ContextNode &Node);
66+
void writeImpl(std::optional<uint32_t> CallerIndex,
67+
const ctx_profile::ContextNode &Node);
68+
69+
public:
70+
PGOCtxProfileWriter(raw_fd_stream &Out,
71+
std::optional<unsigned> VersionOverride = std::nullopt)
72+
: Writer(Buff, &Out, 0) {
73+
Writer.EnterSubblock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID,
74+
CodeLen);
75+
const auto Version = VersionOverride ? *VersionOverride : CurrentVersion;
76+
Writer.EmitRecord(PGOCtxProfileRecords::Version,
77+
SmallVector<unsigned, 1>({Version}));
78+
}
79+
80+
~PGOCtxProfileWriter() { Writer.ExitBlock(); }
81+
82+
void write(const ctx_profile::ContextNode &);
83+
84+
// constants used in writing which a reader may find useful.
85+
static constexpr unsigned CodeLen = 2;
86+
static constexpr uint32_t CurrentVersion = 1;
87+
static constexpr unsigned VBREncodingBits = 6;
88+
};
89+
90+
} // namespace llvm
91+
#endif

llvm/lib/ProfileData/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ add_llvm_component_library(LLVMProfileData
77
ItaniumManglingCanonicalizer.cpp
88
MemProf.cpp
99
MemProfReader.cpp
10+
PGOCtxProfReader.cpp
11+
PGOCtxProfWriter.cpp
1012
ProfileSummaryBuilder.cpp
1113
SampleProf.cpp
1214
SampleProfReader.cpp
@@ -20,6 +22,7 @@ add_llvm_component_library(LLVMProfileData
2022
intrinsics_gen
2123

2224
LINK_COMPONENTS
25+
BitstreamReader
2326
Core
2427
Object
2528
Support
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//===- PGOCtxProfReader.cpp - Contextual Instrumentation profile reader ---===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Read a contextual profile into a datastructure suitable for maintenance
10+
// throughout IPO
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "llvm/ProfileData/PGOCtxProfReader.h"
15+
#include "llvm/Bitstream/BitCodeEnums.h"
16+
#include "llvm/Bitstream/BitstreamReader.h"
17+
#include "llvm/ProfileData/InstrProf.h"
18+
#include "llvm/ProfileData/PGOCtxProfWriter.h"
19+
#include "llvm/Support/Errc.h"
20+
#include "llvm/Support/Error.h"
21+
22+
using namespace llvm;
23+
24+
// FIXME(#92054) - these Error handling macros are (re-)invented in a few
25+
// places.
26+
#define EXPECT_OR_RET(LHS, RHS) \
27+
auto LHS = RHS; \
28+
if (!LHS) \
29+
return LHS.takeError();
30+
31+
#define RET_ON_ERR(EXPR) \
32+
if (auto Err = (EXPR)) \
33+
return Err;
34+
35+
Expected<PGOContextualProfile &>
36+
PGOContextualProfile::getOrEmplace(uint32_t Index, GlobalValue::GUID G,
37+
SmallVectorImpl<uint64_t> &&Counters) {
38+
auto [Iter, Inserted] = Callsites[Index].insert(
39+
{G, PGOContextualProfile(G, std::move(Counters))});
40+
if (!Inserted)
41+
return make_error<InstrProfError>(instrprof_error::invalid_prof,
42+
"Duplicate GUID for same callsite.");
43+
return Iter->second;
44+
}
45+
46+
void PGOContextualProfile::getContainedGuids(
47+
DenseSet<GlobalValue::GUID> &Guids) const {
48+
Guids.insert(GUID);
49+
for (const auto &[_, Callsite] : Callsites)
50+
for (const auto &[_, Callee] : Callsite)
51+
Callee.getContainedGuids(Guids);
52+
}
53+
54+
Expected<BitstreamEntry> PGOCtxProfileReader::advance() {
55+
return Cursor.advance(BitstreamCursor::AF_DontAutoprocessAbbrevs);
56+
}
57+
58+
Error PGOCtxProfileReader::wrongValue(const Twine &Msg) {
59+
return make_error<InstrProfError>(instrprof_error::invalid_prof, Msg);
60+
}
61+
62+
Error PGOCtxProfileReader::unsupported(const Twine &Msg) {
63+
return make_error<InstrProfError>(instrprof_error::unsupported_version, Msg);
64+
}
65+
66+
bool PGOCtxProfileReader::canReadContext() {
67+
auto Blk = advance();
68+
if (!Blk) {
69+
consumeError(Blk.takeError());
70+
return false;
71+
}
72+
return Blk->Kind == BitstreamEntry::SubBlock &&
73+
Blk->ID == PGOCtxProfileBlockIDs::ContextNodeBlockID;
74+
}
75+
76+
Expected<std::pair<std::optional<uint32_t>, PGOContextualProfile>>
77+
PGOCtxProfileReader::readContext(bool ExpectIndex) {
78+
RET_ON_ERR(Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ContextNodeBlockID));
79+
80+
std::optional<ctx_profile::GUID> Guid;
81+
std::optional<SmallVector<uint64_t, 16>> Counters;
82+
std::optional<uint32_t> CallsiteIndex;
83+
84+
SmallVector<uint64_t, 1> RecordValues;
85+
86+
// We don't prescribe the order in which the records come in, and we are ok
87+
// if other unsupported records appear. We seek in the current subblock until
88+
// we get all we know.
89+
auto GotAllWeNeed = [&]() {
90+
return Guid.has_value() && Counters.has_value() &&
91+
(!ExpectIndex || CallsiteIndex.has_value());
92+
};
93+
while (!GotAllWeNeed()) {
94+
RecordValues.clear();
95+
EXPECT_OR_RET(Entry, advance());
96+
if (Entry->Kind != BitstreamEntry::Record)
97+
return wrongValue(
98+
"Expected records before encountering more subcontexts");
99+
EXPECT_OR_RET(ReadRecord,
100+
Cursor.readRecord(bitc::UNABBREV_RECORD, RecordValues));
101+
switch (*ReadRecord) {
102+
case PGOCtxProfileRecords::Guid:
103+
if (RecordValues.size() != 1)
104+
return wrongValue("The GUID record should have exactly one value");
105+
Guid = RecordValues[0];
106+
break;
107+
case PGOCtxProfileRecords::Counters:
108+
Counters = std::move(RecordValues);
109+
if (Counters->empty())
110+
return wrongValue("Empty counters. At least the entry counter (one "
111+
"value) was expected");
112+
break;
113+
case PGOCtxProfileRecords::CalleeIndex:
114+
if (!ExpectIndex)
115+
return wrongValue("The root context should not have a callee index");
116+
if (RecordValues.size() != 1)
117+
return wrongValue("The callee index should have exactly one value");
118+
CallsiteIndex = RecordValues[0];
119+
break;
120+
default:
121+
// OK if we see records we do not understand, like records (profile
122+
// components) introduced later.
123+
break;
124+
}
125+
}
126+
127+
PGOContextualProfile Ret(*Guid, std::move(*Counters));
128+
129+
while (canReadContext()) {
130+
EXPECT_OR_RET(SC, readContext(true));
131+
auto &Targets = Ret.callsites()[*SC->first];
132+
auto [_, Inserted] =
133+
Targets.insert({SC->second.guid(), std::move(SC->second)});
134+
if (!Inserted)
135+
return wrongValue(
136+
"Unexpected duplicate target (callee) at the same callsite.");
137+
}
138+
return std::make_pair(CallsiteIndex, std::move(Ret));
139+
}
140+
141+
Error PGOCtxProfileReader::readMetadata() {
142+
EXPECT_OR_RET(Blk, advance());
143+
if (Blk->Kind != BitstreamEntry::SubBlock)
144+
return unsupported("Expected Version record");
145+
RET_ON_ERR(
146+
Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID));
147+
EXPECT_OR_RET(MData, advance());
148+
if (MData->Kind != BitstreamEntry::Record)
149+
return unsupported("Expected Version record");
150+
151+
SmallVector<uint64_t, 1> Ver;
152+
EXPECT_OR_RET(Code, Cursor.readRecord(bitc::UNABBREV_RECORD, Ver));
153+
if (*Code != PGOCtxProfileRecords::Version)
154+
return unsupported("Expected Version record");
155+
if (Ver.size() != 1 || Ver[0] > PGOCtxProfileWriter::CurrentVersion)
156+
return unsupported("Version " + Twine(*Code) +
157+
" is higher than supported version " +
158+
Twine(PGOCtxProfileWriter::CurrentVersion));
159+
return Error::success();
160+
}
161+
162+
Expected<std::map<GlobalValue::GUID, PGOContextualProfile>>
163+
PGOCtxProfileReader::loadContexts() {
164+
std::map<GlobalValue::GUID, PGOContextualProfile> Ret;
165+
RET_ON_ERR(readMetadata());
166+
while (canReadContext()) {
167+
EXPECT_OR_RET(E, readContext(false));
168+
auto Key = E->second.guid();
169+
if (!Ret.insert({Key, std::move(E->second)}).second)
170+
return wrongValue("Duplicate roots");
171+
}
172+
return Ret;
173+
}

0 commit comments

Comments
 (0)