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 <utility> 23 24 using namespace llvm; 25 26 // FIXME(#92054) - these Error handling macros are (re-)invented in a few 27 // places. 28 #define EXPECT_OR_RET(LHS, RHS) \ 29 auto LHS = RHS; \ 30 if (!LHS) \ 31 return LHS.takeError(); 32 33 #define RET_ON_ERR(EXPR) \ 34 if (auto Err = (EXPR)) \ 35 return Err; 36 37 Expected<PGOCtxProfContext &> 38 PGOCtxProfContext::getOrEmplace(uint32_t Index, GlobalValue::GUID G, 39 SmallVectorImpl<uint64_t> &&Counters) { 40 auto [Iter, Inserted] = 41 Callsites[Index].insert({G, PGOCtxProfContext(G, std::move(Counters))}); 42 if (!Inserted) 43 return make_error<InstrProfError>(instrprof_error::invalid_prof, 44 "Duplicate GUID for same callsite."); 45 return Iter->second; 46 } 47 48 Expected<BitstreamEntry> PGOCtxProfileReader::advance() { 49 return Cursor.advance(BitstreamCursor::AF_DontAutoprocessAbbrevs); 50 } 51 52 Error PGOCtxProfileReader::wrongValue(const Twine &Msg) { 53 return make_error<InstrProfError>(instrprof_error::invalid_prof, Msg); 54 } 55 56 Error PGOCtxProfileReader::unsupported(const Twine &Msg) { 57 return make_error<InstrProfError>(instrprof_error::unsupported_version, Msg); 58 } 59 60 bool PGOCtxProfileReader::tryGetNextKnownBlockID(PGOCtxProfileBlockIDs &ID) { 61 auto Blk = advance(); 62 if (!Blk) { 63 consumeError(Blk.takeError()); 64 return false; 65 } 66 if (Blk->Kind != BitstreamEntry::SubBlock) 67 return false; 68 if (PGOCtxProfileBlockIDs::FIRST_VALID > Blk->ID || 69 PGOCtxProfileBlockIDs::LAST_VALID < Blk->ID) 70 return false; 71 ID = static_cast<PGOCtxProfileBlockIDs>(Blk->ID); 72 return true; 73 } 74 75 bool PGOCtxProfileReader::canEnterBlockWithID(PGOCtxProfileBlockIDs ID) { 76 PGOCtxProfileBlockIDs Test = {}; 77 return tryGetNextKnownBlockID(Test) && Test == ID; 78 } 79 80 Error PGOCtxProfileReader::enterBlockWithID(PGOCtxProfileBlockIDs ID) { 81 RET_ON_ERR(Cursor.EnterSubBlock(ID)); 82 return Error::success(); 83 } 84 85 // Note: we use PGOCtxProfContext for flat profiles also, as the latter are 86 // structurally similar. Alternative modeling here seems a bit overkill at the 87 // moment. 88 Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>> 89 PGOCtxProfileReader::readProfile(PGOCtxProfileBlockIDs Kind) { 90 assert((Kind == PGOCtxProfileBlockIDs::ContextRootBlockID || 91 Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID || 92 Kind == PGOCtxProfileBlockIDs::FlatProfileBlockID) && 93 "Unexpected profile kind"); 94 RET_ON_ERR(enterBlockWithID(Kind)); 95 96 std::optional<ctx_profile::GUID> Guid; 97 std::optional<SmallVector<uint64_t, 16>> Counters; 98 std::optional<uint32_t> CallsiteIndex; 99 std::optional<uint64_t> TotalEntryCount; 100 std::optional<CtxProfFlatProfile> Unhandled; 101 SmallVector<uint64_t, 1> RecordValues; 102 103 const bool ExpectIndex = Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID; 104 const bool IsRoot = Kind == PGOCtxProfileBlockIDs::ContextRootBlockID; 105 // We don't prescribe the order in which the records come in, and we are ok 106 // if other unsupported records appear. We seek in the current subblock until 107 // we get all we know. 108 auto GotAllWeNeed = [&]() { 109 return Guid.has_value() && Counters.has_value() && 110 (!ExpectIndex || CallsiteIndex.has_value()) && 111 (!IsRoot || TotalEntryCount.has_value()) && 112 (!IsRoot || Unhandled.has_value()); 113 }; 114 115 while (!GotAllWeNeed()) { 116 RecordValues.clear(); 117 EXPECT_OR_RET(Entry, advance()); 118 if (Entry->Kind != BitstreamEntry::Record) { 119 if (IsRoot && Entry->Kind == BitstreamEntry::SubBlock && 120 Entry->ID == PGOCtxProfileBlockIDs::UnhandledBlockID) { 121 RET_ON_ERR(enterBlockWithID(PGOCtxProfileBlockIDs::UnhandledBlockID)); 122 Unhandled = CtxProfFlatProfile(); 123 RET_ON_ERR(loadFlatProfileList(*Unhandled)); 124 continue; 125 } 126 return wrongValue( 127 "Expected records before encountering more subcontexts"); 128 } 129 EXPECT_OR_RET(ReadRecord, 130 Cursor.readRecord(bitc::UNABBREV_RECORD, RecordValues)); 131 switch (*ReadRecord) { 132 case PGOCtxProfileRecords::Guid: 133 if (RecordValues.size() != 1) 134 return wrongValue("The GUID record should have exactly one value"); 135 Guid = RecordValues[0]; 136 break; 137 case PGOCtxProfileRecords::Counters: 138 Counters = std::move(RecordValues); 139 if (Counters->empty()) 140 return wrongValue("Empty counters. At least the entry counter (one " 141 "value) was expected"); 142 break; 143 case PGOCtxProfileRecords::CallsiteIndex: 144 if (!ExpectIndex) 145 return wrongValue("The root context should not have a callee index"); 146 if (RecordValues.size() != 1) 147 return wrongValue("The callee index should have exactly one value"); 148 CallsiteIndex = RecordValues[0]; 149 break; 150 case PGOCtxProfileRecords::TotalRootEntryCount: 151 if (!IsRoot) 152 return wrongValue("Non-root has a total entry count record"); 153 if (RecordValues.size() != 1) 154 return wrongValue( 155 "The root total entry count record should have exactly one value"); 156 TotalEntryCount = RecordValues[0]; 157 break; 158 default: 159 // OK if we see records we do not understand, like records (profile 160 // components) introduced later. 161 break; 162 } 163 } 164 165 PGOCtxProfContext Ret(*Guid, std::move(*Counters), TotalEntryCount, 166 std::move(Unhandled)); 167 168 while (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextNodeBlockID)) { 169 EXPECT_OR_RET(SC, readProfile(PGOCtxProfileBlockIDs::ContextNodeBlockID)); 170 auto &Targets = Ret.callsites()[*SC->first]; 171 auto [_, Inserted] = 172 Targets.insert({SC->second.guid(), std::move(SC->second)}); 173 if (!Inserted) 174 return wrongValue( 175 "Unexpected duplicate target (callee) at the same callsite."); 176 } 177 return std::make_pair(CallsiteIndex, std::move(Ret)); 178 } 179 180 Error PGOCtxProfileReader::readMetadata() { 181 if (Magic.size() < PGOCtxProfileWriter::ContainerMagic.size() || 182 Magic != PGOCtxProfileWriter::ContainerMagic) 183 return make_error<InstrProfError>(instrprof_error::invalid_prof, 184 "Invalid magic"); 185 186 BitstreamEntry Entry; 187 RET_ON_ERR(Cursor.advance().moveInto(Entry)); 188 if (Entry.Kind != BitstreamEntry::SubBlock || 189 Entry.ID != bitc::BLOCKINFO_BLOCK_ID) 190 return unsupported("Expected Block ID"); 191 // We don't need the blockinfo to read the rest, it's metadata usable for e.g. 192 // llvm-bcanalyzer. 193 RET_ON_ERR(Cursor.SkipBlock()); 194 195 EXPECT_OR_RET(Blk, advance()); 196 if (Blk->Kind != BitstreamEntry::SubBlock) 197 return unsupported("Expected Version record"); 198 RET_ON_ERR( 199 Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID)); 200 EXPECT_OR_RET(MData, advance()); 201 if (MData->Kind != BitstreamEntry::Record) 202 return unsupported("Expected Version record"); 203 204 SmallVector<uint64_t, 1> Ver; 205 EXPECT_OR_RET(Code, Cursor.readRecord(bitc::UNABBREV_RECORD, Ver)); 206 if (*Code != PGOCtxProfileRecords::Version) 207 return unsupported("Expected Version record"); 208 if (Ver.size() != 1 || Ver[0] > PGOCtxProfileWriter::CurrentVersion) 209 return unsupported("Version " + Twine(*Code) + 210 " is higher than supported version " + 211 Twine(PGOCtxProfileWriter::CurrentVersion)); 212 return Error::success(); 213 } 214 215 Error PGOCtxProfileReader::loadContexts(CtxProfContextualProfiles &P) { 216 RET_ON_ERR(enterBlockWithID(PGOCtxProfileBlockIDs::ContextsSectionBlockID)); 217 while (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextRootBlockID)) { 218 EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::ContextRootBlockID)); 219 auto Key = E->second.guid(); 220 if (!P.insert({Key, std::move(E->second)}).second) 221 return wrongValue("Duplicate roots"); 222 } 223 return Error::success(); 224 } 225 226 Error PGOCtxProfileReader::loadFlatProfileList(CtxProfFlatProfile &P) { 227 while (canEnterBlockWithID(PGOCtxProfileBlockIDs::FlatProfileBlockID)) { 228 EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::FlatProfileBlockID)); 229 auto Guid = E->second.guid(); 230 if (!P.insert({Guid, std::move(E->second.counters())}).second) 231 return wrongValue("Duplicate flat profile entries"); 232 } 233 return Error::success(); 234 } 235 236 Error PGOCtxProfileReader::loadFlatProfiles(CtxProfFlatProfile &P) { 237 RET_ON_ERR( 238 enterBlockWithID(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID)); 239 return loadFlatProfileList(P); 240 } 241 242 Expected<PGOCtxProfile> PGOCtxProfileReader::loadProfiles() { 243 RET_ON_ERR(readMetadata()); 244 PGOCtxProfile Ret; 245 PGOCtxProfileBlockIDs Test = {}; 246 for (auto I = 0; I < 2; ++I) { 247 if (!tryGetNextKnownBlockID(Test)) 248 break; 249 if (Test == PGOCtxProfileBlockIDs::ContextsSectionBlockID) { 250 RET_ON_ERR(loadContexts(Ret.Contexts)); 251 } else if (Test == PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID) { 252 RET_ON_ERR(loadFlatProfiles(Ret.FlatProfiles)); 253 } else { 254 return wrongValue("Unexpected section"); 255 } 256 } 257 258 return std::move(Ret); 259 } 260 261 namespace { 262 // We want to pass `const` values PGOCtxProfContext references to the yaml 263 // converter, and the regular yaml mapping APIs are designed to handle both 264 // serialization and deserialization, which prevents using const for 265 // serialization. Using an intermediate datastructure is overkill, both 266 // space-wise and design complexity-wise. Instead, we use the lower-level APIs. 267 void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx); 268 269 void toYaml(yaml::Output &Out, 270 const PGOCtxProfContext::CallTargetMapTy &CallTargets) { 271 Out.beginSequence(); 272 size_t Index = 0; 273 void *SaveData = nullptr; 274 for (const auto &[_, Ctx] : CallTargets) { 275 Out.preflightElement(Index++, SaveData); 276 toYaml(Out, Ctx); 277 Out.postflightElement(nullptr); 278 } 279 Out.endSequence(); 280 } 281 282 void toYaml(yaml::Output &Out, 283 const PGOCtxProfContext::CallsiteMapTy &Callsites) { 284 auto AllCS = ::llvm::make_first_range(Callsites); 285 auto MaxIt = ::llvm::max_element(AllCS); 286 assert(MaxIt != AllCS.end() && "We should have a max value because the " 287 "callsites collection is not empty."); 288 void *SaveData = nullptr; 289 Out.beginSequence(); 290 for (auto I = 0U; I <= *MaxIt; ++I) { 291 Out.preflightElement(I, SaveData); 292 auto It = Callsites.find(I); 293 if (It == Callsites.end()) { 294 // This will produce a `[ ]` sequence, which is what we want here. 295 Out.beginFlowSequence(); 296 Out.endFlowSequence(); 297 } else { 298 toYaml(Out, It->second); 299 } 300 Out.postflightElement(nullptr); 301 } 302 Out.endSequence(); 303 } 304 305 void toYaml(yaml::Output &Out, const CtxProfFlatProfile &Flat); 306 307 void toYaml(yaml::Output &Out, GlobalValue::GUID Guid, 308 const SmallVectorImpl<uint64_t> &Counters, 309 const PGOCtxProfContext::CallsiteMapTy &Callsites, 310 std::optional<uint64_t> TotalRootEntryCount = std::nullopt, 311 CtxProfFlatProfile Unhandled = {}) { 312 yaml::EmptyContext Empty; 313 Out.beginMapping(); 314 void *SaveInfo = nullptr; 315 bool UseDefault = false; 316 { 317 Out.preflightKey("Guid", /*Required=*/true, /*SameAsDefault=*/false, 318 UseDefault, SaveInfo); 319 yaml::yamlize(Out, Guid, true, Empty); 320 Out.postflightKey(nullptr); 321 } 322 if (TotalRootEntryCount) { 323 Out.preflightKey("TotalRootEntryCount", true, false, UseDefault, SaveInfo); 324 yaml::yamlize(Out, *TotalRootEntryCount, true, Empty); 325 Out.postflightKey(nullptr); 326 } 327 { 328 Out.preflightKey("Counters", true, false, UseDefault, SaveInfo); 329 Out.beginFlowSequence(); 330 for (size_t I = 0U, E = Counters.size(); I < E; ++I) { 331 Out.preflightFlowElement(I, SaveInfo); 332 uint64_t V = Counters[I]; 333 yaml::yamlize(Out, V, true, Empty); 334 Out.postflightFlowElement(SaveInfo); 335 } 336 Out.endFlowSequence(); 337 Out.postflightKey(nullptr); 338 } 339 340 if (!Unhandled.empty()) { 341 assert(TotalRootEntryCount.has_value()); 342 Out.preflightKey("Unhandled", false, false, UseDefault, SaveInfo); 343 toYaml(Out, Unhandled); 344 Out.postflightKey(nullptr); 345 } 346 347 if (!Callsites.empty()) { 348 Out.preflightKey("Callsites", true, false, UseDefault, SaveInfo); 349 toYaml(Out, Callsites); 350 Out.postflightKey(nullptr); 351 } 352 Out.endMapping(); 353 } 354 355 void toYaml(yaml::Output &Out, const CtxProfFlatProfile &Flat) { 356 void *SaveInfo = nullptr; 357 Out.beginSequence(); 358 size_t ElemID = 0; 359 for (const auto &[Guid, Counters] : Flat) { 360 Out.preflightElement(ElemID++, SaveInfo); 361 toYaml(Out, Guid, Counters, {}); 362 Out.postflightElement(nullptr); 363 } 364 Out.endSequence(); 365 } 366 367 void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx) { 368 if (Ctx.isRoot()) 369 toYaml(Out, Ctx.guid(), Ctx.counters(), Ctx.callsites(), 370 Ctx.getTotalRootEntryCount(), Ctx.getUnhandled()); 371 else 372 toYaml(Out, Ctx.guid(), Ctx.counters(), Ctx.callsites()); 373 } 374 375 } // namespace 376 377 void llvm::convertCtxProfToYaml(raw_ostream &OS, const PGOCtxProfile &Profile) { 378 yaml::Output Out(OS); 379 void *SaveInfo = nullptr; 380 bool UseDefault = false; 381 Out.beginMapping(); 382 if (!Profile.Contexts.empty()) { 383 Out.preflightKey("Contexts", false, false, UseDefault, SaveInfo); 384 toYaml(Out, Profile.Contexts); 385 Out.postflightKey(nullptr); 386 } 387 if (!Profile.FlatProfiles.empty()) { 388 Out.preflightKey("FlatProfiles", false, false, UseDefault, SaveInfo); 389 toYaml(Out, Profile.FlatProfiles); 390 Out.postflightKey(nullptr); 391 } 392 Out.endMapping(); 393 } 394