xref: /llvm-project/llvm/lib/ProfileData/PGOCtxProfReader.cpp (revision b15845c0059b06f406e33f278127d7eb41ff5ab6)
1fc8775e2SMircea Trofin //===- PGOCtxProfReader.cpp - Contextual Instrumentation profile reader ---===//
2fc8775e2SMircea Trofin //
3fc8775e2SMircea Trofin // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4fc8775e2SMircea Trofin // See https://llvm.org/LICENSE.txt for license information.
5fc8775e2SMircea Trofin // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6fc8775e2SMircea Trofin //
7fc8775e2SMircea Trofin //===----------------------------------------------------------------------===//
8fc8775e2SMircea Trofin //
9fc8775e2SMircea Trofin // Read a contextual profile into a datastructure suitable for maintenance
10fc8775e2SMircea Trofin // throughout IPO
11fc8775e2SMircea Trofin //
12fc8775e2SMircea Trofin //===----------------------------------------------------------------------===//
13fc8775e2SMircea Trofin 
14fc8775e2SMircea Trofin #include "llvm/ProfileData/PGOCtxProfReader.h"
15fc8775e2SMircea Trofin #include "llvm/Bitstream/BitCodeEnums.h"
16fc8775e2SMircea Trofin #include "llvm/Bitstream/BitstreamReader.h"
17fc8775e2SMircea Trofin #include "llvm/ProfileData/InstrProf.h"
18fc8775e2SMircea Trofin #include "llvm/ProfileData/PGOCtxProfWriter.h"
19fc8775e2SMircea Trofin #include "llvm/Support/Error.h"
20*b15845c0SMircea Trofin #include "llvm/Support/ErrorHandling.h"
21*b15845c0SMircea Trofin #include "llvm/Support/YAMLTraits.h"
22*b15845c0SMircea Trofin #include <iterator>
23*b15845c0SMircea Trofin #include <utility>
24fc8775e2SMircea Trofin 
25fc8775e2SMircea Trofin using namespace llvm;
26fc8775e2SMircea Trofin 
27fc8775e2SMircea Trofin // FIXME(#92054) - these Error handling macros are (re-)invented in a few
28fc8775e2SMircea Trofin // places.
29fc8775e2SMircea Trofin #define EXPECT_OR_RET(LHS, RHS)                                                \
30fc8775e2SMircea Trofin   auto LHS = RHS;                                                              \
31fc8775e2SMircea Trofin   if (!LHS)                                                                    \
32fc8775e2SMircea Trofin     return LHS.takeError();
33fc8775e2SMircea Trofin 
34fc8775e2SMircea Trofin #define RET_ON_ERR(EXPR)                                                       \
35fc8775e2SMircea Trofin   if (auto Err = (EXPR))                                                       \
36fc8775e2SMircea Trofin     return Err;
37fc8775e2SMircea Trofin 
386b47772aSMircea Trofin Expected<PGOCtxProfContext &>
396b47772aSMircea Trofin PGOCtxProfContext::getOrEmplace(uint32_t Index, GlobalValue::GUID G,
40fc8775e2SMircea Trofin                                 SmallVectorImpl<uint64_t> &&Counters) {
416b47772aSMircea Trofin   auto [Iter, Inserted] =
426b47772aSMircea Trofin       Callsites[Index].insert({G, PGOCtxProfContext(G, std::move(Counters))});
43fc8775e2SMircea Trofin   if (!Inserted)
44fc8775e2SMircea Trofin     return make_error<InstrProfError>(instrprof_error::invalid_prof,
45fc8775e2SMircea Trofin                                       "Duplicate GUID for same callsite.");
46fc8775e2SMircea Trofin   return Iter->second;
47fc8775e2SMircea Trofin }
48fc8775e2SMircea Trofin 
49fc8775e2SMircea Trofin Expected<BitstreamEntry> PGOCtxProfileReader::advance() {
50fc8775e2SMircea Trofin   return Cursor.advance(BitstreamCursor::AF_DontAutoprocessAbbrevs);
51fc8775e2SMircea Trofin }
52fc8775e2SMircea Trofin 
53fc8775e2SMircea Trofin Error PGOCtxProfileReader::wrongValue(const Twine &Msg) {
54fc8775e2SMircea Trofin   return make_error<InstrProfError>(instrprof_error::invalid_prof, Msg);
55fc8775e2SMircea Trofin }
56fc8775e2SMircea Trofin 
57fc8775e2SMircea Trofin Error PGOCtxProfileReader::unsupported(const Twine &Msg) {
58fc8775e2SMircea Trofin   return make_error<InstrProfError>(instrprof_error::unsupported_version, Msg);
59fc8775e2SMircea Trofin }
60fc8775e2SMircea Trofin 
61fc8775e2SMircea Trofin bool PGOCtxProfileReader::canReadContext() {
62fc8775e2SMircea Trofin   auto Blk = advance();
63fc8775e2SMircea Trofin   if (!Blk) {
64fc8775e2SMircea Trofin     consumeError(Blk.takeError());
65fc8775e2SMircea Trofin     return false;
66fc8775e2SMircea Trofin   }
67fc8775e2SMircea Trofin   return Blk->Kind == BitstreamEntry::SubBlock &&
68fc8775e2SMircea Trofin          Blk->ID == PGOCtxProfileBlockIDs::ContextNodeBlockID;
69fc8775e2SMircea Trofin }
70fc8775e2SMircea Trofin 
716b47772aSMircea Trofin Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>>
72fc8775e2SMircea Trofin PGOCtxProfileReader::readContext(bool ExpectIndex) {
73fc8775e2SMircea Trofin   RET_ON_ERR(Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ContextNodeBlockID));
74fc8775e2SMircea Trofin 
75fc8775e2SMircea Trofin   std::optional<ctx_profile::GUID> Guid;
76fc8775e2SMircea Trofin   std::optional<SmallVector<uint64_t, 16>> Counters;
77fc8775e2SMircea Trofin   std::optional<uint32_t> CallsiteIndex;
78fc8775e2SMircea Trofin 
79fc8775e2SMircea Trofin   SmallVector<uint64_t, 1> RecordValues;
80fc8775e2SMircea Trofin 
81fc8775e2SMircea Trofin   // We don't prescribe the order in which the records come in, and we are ok
82fc8775e2SMircea Trofin   // if other unsupported records appear. We seek in the current subblock until
83fc8775e2SMircea Trofin   // we get all we know.
84fc8775e2SMircea Trofin   auto GotAllWeNeed = [&]() {
85fc8775e2SMircea Trofin     return Guid.has_value() && Counters.has_value() &&
86fc8775e2SMircea Trofin            (!ExpectIndex || CallsiteIndex.has_value());
87fc8775e2SMircea Trofin   };
88fc8775e2SMircea Trofin   while (!GotAllWeNeed()) {
89fc8775e2SMircea Trofin     RecordValues.clear();
90fc8775e2SMircea Trofin     EXPECT_OR_RET(Entry, advance());
91fc8775e2SMircea Trofin     if (Entry->Kind != BitstreamEntry::Record)
92fc8775e2SMircea Trofin       return wrongValue(
93fc8775e2SMircea Trofin           "Expected records before encountering more subcontexts");
94fc8775e2SMircea Trofin     EXPECT_OR_RET(ReadRecord,
95fc8775e2SMircea Trofin                   Cursor.readRecord(bitc::UNABBREV_RECORD, RecordValues));
96fc8775e2SMircea Trofin     switch (*ReadRecord) {
97fc8775e2SMircea Trofin     case PGOCtxProfileRecords::Guid:
98fc8775e2SMircea Trofin       if (RecordValues.size() != 1)
99fc8775e2SMircea Trofin         return wrongValue("The GUID record should have exactly one value");
100fc8775e2SMircea Trofin       Guid = RecordValues[0];
101fc8775e2SMircea Trofin       break;
102fc8775e2SMircea Trofin     case PGOCtxProfileRecords::Counters:
103fc8775e2SMircea Trofin       Counters = std::move(RecordValues);
104fc8775e2SMircea Trofin       if (Counters->empty())
105fc8775e2SMircea Trofin         return wrongValue("Empty counters. At least the entry counter (one "
106fc8775e2SMircea Trofin                           "value) was expected");
107fc8775e2SMircea Trofin       break;
108fc8775e2SMircea Trofin     case PGOCtxProfileRecords::CalleeIndex:
109fc8775e2SMircea Trofin       if (!ExpectIndex)
110fc8775e2SMircea Trofin         return wrongValue("The root context should not have a callee index");
111fc8775e2SMircea Trofin       if (RecordValues.size() != 1)
112fc8775e2SMircea Trofin         return wrongValue("The callee index should have exactly one value");
113fc8775e2SMircea Trofin       CallsiteIndex = RecordValues[0];
114fc8775e2SMircea Trofin       break;
115fc8775e2SMircea Trofin     default:
116fc8775e2SMircea Trofin       // OK if we see records we do not understand, like records (profile
117fc8775e2SMircea Trofin       // components) introduced later.
118fc8775e2SMircea Trofin       break;
119fc8775e2SMircea Trofin     }
120fc8775e2SMircea Trofin   }
121fc8775e2SMircea Trofin 
1226b47772aSMircea Trofin   PGOCtxProfContext Ret(*Guid, std::move(*Counters));
123fc8775e2SMircea Trofin 
124fc8775e2SMircea Trofin   while (canReadContext()) {
125fc8775e2SMircea Trofin     EXPECT_OR_RET(SC, readContext(true));
126fc8775e2SMircea Trofin     auto &Targets = Ret.callsites()[*SC->first];
127fc8775e2SMircea Trofin     auto [_, Inserted] =
128fc8775e2SMircea Trofin         Targets.insert({SC->second.guid(), std::move(SC->second)});
129fc8775e2SMircea Trofin     if (!Inserted)
130fc8775e2SMircea Trofin       return wrongValue(
131fc8775e2SMircea Trofin           "Unexpected duplicate target (callee) at the same callsite.");
132fc8775e2SMircea Trofin   }
133fc8775e2SMircea Trofin   return std::make_pair(CallsiteIndex, std::move(Ret));
134fc8775e2SMircea Trofin }
135fc8775e2SMircea Trofin 
136fc8775e2SMircea Trofin Error PGOCtxProfileReader::readMetadata() {
137cc7308a1SMircea Trofin   if (Magic.size() < PGOCtxProfileWriter::ContainerMagic.size() ||
138cc7308a1SMircea Trofin       Magic != PGOCtxProfileWriter::ContainerMagic)
139cc7308a1SMircea Trofin     return make_error<InstrProfError>(instrprof_error::invalid_prof,
140cc7308a1SMircea Trofin                                       "Invalid magic");
141cc7308a1SMircea Trofin 
142cc7308a1SMircea Trofin   BitstreamEntry Entry;
143cc7308a1SMircea Trofin   RET_ON_ERR(Cursor.advance().moveInto(Entry));
144cc7308a1SMircea Trofin   if (Entry.Kind != BitstreamEntry::SubBlock ||
145cc7308a1SMircea Trofin       Entry.ID != bitc::BLOCKINFO_BLOCK_ID)
146cc7308a1SMircea Trofin     return unsupported("Expected Block ID");
147cc7308a1SMircea Trofin   // We don't need the blockinfo to read the rest, it's metadata usable for e.g.
148cc7308a1SMircea Trofin   // llvm-bcanalyzer.
149cc7308a1SMircea Trofin   RET_ON_ERR(Cursor.SkipBlock());
150cc7308a1SMircea Trofin 
151fc8775e2SMircea Trofin   EXPECT_OR_RET(Blk, advance());
152fc8775e2SMircea Trofin   if (Blk->Kind != BitstreamEntry::SubBlock)
153fc8775e2SMircea Trofin     return unsupported("Expected Version record");
154fc8775e2SMircea Trofin   RET_ON_ERR(
155fc8775e2SMircea Trofin       Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID));
156fc8775e2SMircea Trofin   EXPECT_OR_RET(MData, advance());
157fc8775e2SMircea Trofin   if (MData->Kind != BitstreamEntry::Record)
158fc8775e2SMircea Trofin     return unsupported("Expected Version record");
159fc8775e2SMircea Trofin 
160fc8775e2SMircea Trofin   SmallVector<uint64_t, 1> Ver;
161fc8775e2SMircea Trofin   EXPECT_OR_RET(Code, Cursor.readRecord(bitc::UNABBREV_RECORD, Ver));
162fc8775e2SMircea Trofin   if (*Code != PGOCtxProfileRecords::Version)
163fc8775e2SMircea Trofin     return unsupported("Expected Version record");
164fc8775e2SMircea Trofin   if (Ver.size() != 1 || Ver[0] > PGOCtxProfileWriter::CurrentVersion)
165fc8775e2SMircea Trofin     return unsupported("Version " + Twine(*Code) +
166fc8775e2SMircea Trofin                        " is higher than supported version " +
167fc8775e2SMircea Trofin                        Twine(PGOCtxProfileWriter::CurrentVersion));
168fc8775e2SMircea Trofin   return Error::success();
169fc8775e2SMircea Trofin }
170fc8775e2SMircea Trofin 
1716b47772aSMircea Trofin Expected<std::map<GlobalValue::GUID, PGOCtxProfContext>>
172fc8775e2SMircea Trofin PGOCtxProfileReader::loadContexts() {
1736b47772aSMircea Trofin   std::map<GlobalValue::GUID, PGOCtxProfContext> Ret;
174fc8775e2SMircea Trofin   RET_ON_ERR(readMetadata());
175fc8775e2SMircea Trofin   while (canReadContext()) {
176fc8775e2SMircea Trofin     EXPECT_OR_RET(E, readContext(false));
177fc8775e2SMircea Trofin     auto Key = E->second.guid();
178fc8775e2SMircea Trofin     if (!Ret.insert({Key, std::move(E->second)}).second)
179fc8775e2SMircea Trofin       return wrongValue("Duplicate roots");
180fc8775e2SMircea Trofin   }
181fc8775e2SMircea Trofin   return std::move(Ret);
182fc8775e2SMircea Trofin }
183*b15845c0SMircea Trofin 
184*b15845c0SMircea Trofin namespace {
185*b15845c0SMircea Trofin // We want to pass `const` values PGOCtxProfContext references to the yaml
186*b15845c0SMircea Trofin // converter, and the regular yaml mapping APIs are designed to handle both
187*b15845c0SMircea Trofin // serialization and deserialization, which prevents using const for
188*b15845c0SMircea Trofin // serialization. Using an intermediate datastructure is overkill, both
189*b15845c0SMircea Trofin // space-wise and design complexity-wise. Instead, we use the lower-level APIs.
190*b15845c0SMircea Trofin void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx);
191*b15845c0SMircea Trofin 
192*b15845c0SMircea Trofin void toYaml(yaml::Output &Out,
193*b15845c0SMircea Trofin             const PGOCtxProfContext::CallTargetMapTy &CallTargets) {
194*b15845c0SMircea Trofin   Out.beginSequence();
195*b15845c0SMircea Trofin   size_t Index = 0;
196*b15845c0SMircea Trofin   void *SaveData = nullptr;
197*b15845c0SMircea Trofin   for (const auto &[_, Ctx] : CallTargets) {
198*b15845c0SMircea Trofin     Out.preflightElement(Index++, SaveData);
199*b15845c0SMircea Trofin     toYaml(Out, Ctx);
200*b15845c0SMircea Trofin     Out.postflightElement(nullptr);
201*b15845c0SMircea Trofin   }
202*b15845c0SMircea Trofin   Out.endSequence();
203*b15845c0SMircea Trofin }
204*b15845c0SMircea Trofin 
205*b15845c0SMircea Trofin void toYaml(yaml::Output &Out,
206*b15845c0SMircea Trofin             const PGOCtxProfContext::CallsiteMapTy &Callsites) {
207*b15845c0SMircea Trofin   auto AllCS = ::llvm::make_first_range(Callsites);
208*b15845c0SMircea Trofin   auto MaxIt = ::llvm::max_element(AllCS);
209*b15845c0SMircea Trofin   assert(MaxIt != AllCS.end() && "We should have a max value because the "
210*b15845c0SMircea Trofin                                  "callsites collection is not empty.");
211*b15845c0SMircea Trofin   void *SaveData = nullptr;
212*b15845c0SMircea Trofin   Out.beginSequence();
213*b15845c0SMircea Trofin   for (auto I = 0U; I <= *MaxIt; ++I) {
214*b15845c0SMircea Trofin     Out.preflightElement(I, SaveData);
215*b15845c0SMircea Trofin     auto It = Callsites.find(I);
216*b15845c0SMircea Trofin     if (It == Callsites.end()) {
217*b15845c0SMircea Trofin       // This will produce a `[ ]` sequence, which is what we want here.
218*b15845c0SMircea Trofin       Out.beginFlowSequence();
219*b15845c0SMircea Trofin       Out.endFlowSequence();
220*b15845c0SMircea Trofin     } else {
221*b15845c0SMircea Trofin       toYaml(Out, It->second);
222*b15845c0SMircea Trofin     }
223*b15845c0SMircea Trofin     Out.postflightElement(nullptr);
224*b15845c0SMircea Trofin   }
225*b15845c0SMircea Trofin   Out.endSequence();
226*b15845c0SMircea Trofin }
227*b15845c0SMircea Trofin 
228*b15845c0SMircea Trofin void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx) {
229*b15845c0SMircea Trofin   yaml::EmptyContext Empty;
230*b15845c0SMircea Trofin   Out.beginMapping();
231*b15845c0SMircea Trofin   void *SaveInfo = nullptr;
232*b15845c0SMircea Trofin   bool UseDefault = false;
233*b15845c0SMircea Trofin   {
234*b15845c0SMircea Trofin     Out.preflightKey("Guid", /*Required=*/true, /*SameAsDefault=*/false,
235*b15845c0SMircea Trofin                      UseDefault, SaveInfo);
236*b15845c0SMircea Trofin     auto Guid = Ctx.guid();
237*b15845c0SMircea Trofin     yaml::yamlize(Out, Guid, true, Empty);
238*b15845c0SMircea Trofin     Out.postflightKey(nullptr);
239*b15845c0SMircea Trofin   }
240*b15845c0SMircea Trofin   {
241*b15845c0SMircea Trofin     Out.preflightKey("Counters", true, false, UseDefault, SaveInfo);
242*b15845c0SMircea Trofin     Out.beginFlowSequence();
243*b15845c0SMircea Trofin     for (size_t I = 0U, E = Ctx.counters().size(); I < E; ++I) {
244*b15845c0SMircea Trofin       Out.preflightFlowElement(I, SaveInfo);
245*b15845c0SMircea Trofin       uint64_t V = Ctx.counters()[I];
246*b15845c0SMircea Trofin       yaml::yamlize(Out, V, true, Empty);
247*b15845c0SMircea Trofin       Out.postflightFlowElement(SaveInfo);
248*b15845c0SMircea Trofin     }
249*b15845c0SMircea Trofin     Out.endFlowSequence();
250*b15845c0SMircea Trofin     Out.postflightKey(nullptr);
251*b15845c0SMircea Trofin   }
252*b15845c0SMircea Trofin   if (!Ctx.callsites().empty()) {
253*b15845c0SMircea Trofin     Out.preflightKey("Callsites", true, false, UseDefault, SaveInfo);
254*b15845c0SMircea Trofin     toYaml(Out, Ctx.callsites());
255*b15845c0SMircea Trofin     Out.postflightKey(nullptr);
256*b15845c0SMircea Trofin   }
257*b15845c0SMircea Trofin   Out.endMapping();
258*b15845c0SMircea Trofin }
259*b15845c0SMircea Trofin } // namespace
260*b15845c0SMircea Trofin 
261*b15845c0SMircea Trofin void llvm::convertCtxProfToYaml(
262*b15845c0SMircea Trofin     raw_ostream &OS, const PGOCtxProfContext::CallTargetMapTy &Profiles) {
263*b15845c0SMircea Trofin   yaml::Output Out(OS);
264*b15845c0SMircea Trofin   toYaml(Out, Profiles);
265*b15845c0SMircea Trofin }