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 }