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