| 1 | //===- PGOCtxProfWriter.cpp - Contextual Instrumentation profile writer ---===// |
| 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 | // Write a contextual profile to bitstream. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "llvm/ProfileData/PGOCtxProfWriter.h" |
| 14 | #include "llvm/Bitstream/BitCodeEnums.h" |
| 15 | #include "llvm/ProfileData/CtxInstrContextNode.h" |
| 16 | #include "llvm/Support/CommandLine.h" |
| 17 | #include "llvm/Support/Error.h" |
| 18 | #include "llvm/Support/YAMLTraits.h" |
| 19 | #include "llvm/Support/raw_ostream.h" |
| 20 | |
| 21 | using namespace llvm; |
| 22 | using namespace llvm::ctx_profile; |
| 23 | |
| 24 | static cl::opt<bool> |
| 25 | IncludeEmptyOpt("ctx-prof-include-empty" , cl::init(Val: false), |
| 26 | cl::desc("Also write profiles with all-zero counters. " |
| 27 | "Intended for testing/debugging." )); |
| 28 | |
| 29 | PGOCtxProfileWriter::PGOCtxProfileWriter( |
| 30 | raw_ostream &Out, std::optional<unsigned> VersionOverride, |
| 31 | bool IncludeEmpty) |
| 32 | : Writer(Out, 0), |
| 33 | IncludeEmpty(IncludeEmptyOpt.getNumOccurrences() > 0 ? IncludeEmptyOpt |
| 34 | : IncludeEmpty) { |
| 35 | static_assert(ContainerMagic.size() == 4); |
| 36 | Out.write(Ptr: ContainerMagic.data(), Size: ContainerMagic.size()); |
| 37 | Writer.EnterBlockInfoBlock(); |
| 38 | { |
| 39 | auto DescribeBlock = [&](unsigned ID, StringRef Name) { |
| 40 | Writer.EmitRecord(Code: bitc::BLOCKINFO_CODE_SETBID, |
| 41 | Vals: SmallVector<unsigned, 1>{ID}); |
| 42 | Writer.EmitRecord(Code: bitc::BLOCKINFO_CODE_BLOCKNAME, |
| 43 | Vals: llvm::arrayRefFromStringRef(Input: Name)); |
| 44 | }; |
| 45 | SmallVector<uint64_t, 16> Data; |
| 46 | auto DescribeRecord = [&](unsigned RecordID, StringRef Name) { |
| 47 | Data.clear(); |
| 48 | Data.push_back(Elt: RecordID); |
| 49 | llvm::append_range(C&: Data, R&: Name); |
| 50 | Writer.EmitRecord(Code: bitc::BLOCKINFO_CODE_SETRECORDNAME, Vals: Data); |
| 51 | }; |
| 52 | DescribeBlock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID, "Metadata" ); |
| 53 | DescribeRecord(PGOCtxProfileRecords::Version, "Version" ); |
| 54 | DescribeBlock(PGOCtxProfileBlockIDs::ContextsSectionBlockID, "Contexts" ); |
| 55 | DescribeBlock(PGOCtxProfileBlockIDs::ContextRootBlockID, "Root" ); |
| 56 | DescribeRecord(PGOCtxProfileRecords::Guid, "GUID" ); |
| 57 | DescribeRecord(PGOCtxProfileRecords::TotalRootEntryCount, |
| 58 | "TotalRootEntryCount" ); |
| 59 | DescribeRecord(PGOCtxProfileRecords::Counters, "Counters" ); |
| 60 | DescribeBlock(PGOCtxProfileBlockIDs::UnhandledBlockID, "Unhandled" ); |
| 61 | DescribeBlock(PGOCtxProfileBlockIDs::ContextNodeBlockID, "Context" ); |
| 62 | DescribeRecord(PGOCtxProfileRecords::Guid, "GUID" ); |
| 63 | DescribeRecord(PGOCtxProfileRecords::CallsiteIndex, "CalleeIndex" ); |
| 64 | DescribeRecord(PGOCtxProfileRecords::Counters, "Counters" ); |
| 65 | DescribeBlock(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID, |
| 66 | "FlatProfiles" ); |
| 67 | DescribeBlock(PGOCtxProfileBlockIDs::FlatProfileBlockID, "Flat" ); |
| 68 | DescribeRecord(PGOCtxProfileRecords::Guid, "GUID" ); |
| 69 | DescribeRecord(PGOCtxProfileRecords::Counters, "Counters" ); |
| 70 | } |
| 71 | Writer.ExitBlock(); |
| 72 | Writer.EnterSubblock(BlockID: PGOCtxProfileBlockIDs::ProfileMetadataBlockID, CodeLen); |
| 73 | const auto Version = VersionOverride.value_or(u: CurrentVersion); |
| 74 | Writer.EmitRecord(Code: PGOCtxProfileRecords::Version, |
| 75 | Vals: SmallVector<unsigned, 1>({Version})); |
| 76 | } |
| 77 | |
| 78 | void PGOCtxProfileWriter::writeCounters(ArrayRef<uint64_t> Counters) { |
| 79 | Writer.EmitCode(Val: bitc::UNABBREV_RECORD); |
| 80 | Writer.EmitVBR(Val: PGOCtxProfileRecords::Counters, NumBits: VBREncodingBits); |
| 81 | Writer.EmitVBR(Val: Counters.size(), NumBits: VBREncodingBits); |
| 82 | for (uint64_t C : Counters) |
| 83 | Writer.EmitVBR64(Val: C, NumBits: VBREncodingBits); |
| 84 | } |
| 85 | |
| 86 | void PGOCtxProfileWriter::writeGuid(ctx_profile::GUID Guid) { |
| 87 | Writer.EmitRecord(Code: PGOCtxProfileRecords::Guid, Vals: SmallVector<uint64_t, 1>{Guid}); |
| 88 | } |
| 89 | |
| 90 | void PGOCtxProfileWriter::writeCallsiteIndex(uint32_t CallsiteIndex) { |
| 91 | Writer.EmitRecord(Code: PGOCtxProfileRecords::CallsiteIndex, |
| 92 | Vals: SmallVector<uint64_t, 1>{CallsiteIndex}); |
| 93 | } |
| 94 | |
| 95 | void PGOCtxProfileWriter::writeRootEntryCount(uint64_t TotalRootEntryCount) { |
| 96 | Writer.EmitRecord(Code: PGOCtxProfileRecords::TotalRootEntryCount, |
| 97 | Vals: SmallVector<uint64_t, 1>{TotalRootEntryCount}); |
| 98 | } |
| 99 | |
| 100 | // recursively write all the subcontexts. We do need to traverse depth first to |
| 101 | // model the context->subcontext implicitly, and since this captures call |
| 102 | // stacks, we don't really need to be worried about stack overflow and we can |
| 103 | // keep the implementation simple. |
| 104 | void PGOCtxProfileWriter::writeNode(uint32_t CallsiteIndex, |
| 105 | const ContextNode &Node) { |
| 106 | // A node with no counters is an error. We don't expect this to happen from |
| 107 | // the runtime, rather, this is interesting for testing the reader. |
| 108 | if (!IncludeEmpty && (Node.counters_size() > 0 && Node.entrycount() == 0)) |
| 109 | return; |
| 110 | Writer.EnterSubblock(BlockID: PGOCtxProfileBlockIDs::ContextNodeBlockID, CodeLen); |
| 111 | writeGuid(Guid: Node.guid()); |
| 112 | writeCallsiteIndex(CallsiteIndex); |
| 113 | writeCounters(Counters: {Node.counters(), Node.counters_size()}); |
| 114 | writeSubcontexts(Node); |
| 115 | Writer.ExitBlock(); |
| 116 | } |
| 117 | |
| 118 | void PGOCtxProfileWriter::writeSubcontexts(const ContextNode &Node) { |
| 119 | for (uint32_t I = 0U; I < Node.callsites_size(); ++I) |
| 120 | for (const auto *Subcontext = Node.subContexts()[I]; Subcontext; |
| 121 | Subcontext = Subcontext->next()) |
| 122 | writeNode(CallsiteIndex: I, Node: *Subcontext); |
| 123 | } |
| 124 | |
| 125 | void PGOCtxProfileWriter::startContextSection() { |
| 126 | Writer.EnterSubblock(BlockID: PGOCtxProfileBlockIDs::ContextsSectionBlockID, CodeLen); |
| 127 | } |
| 128 | |
| 129 | void PGOCtxProfileWriter::startFlatSection() { |
| 130 | Writer.EnterSubblock(BlockID: PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID, |
| 131 | CodeLen); |
| 132 | } |
| 133 | |
| 134 | void PGOCtxProfileWriter::endContextSection() { Writer.ExitBlock(); } |
| 135 | void PGOCtxProfileWriter::endFlatSection() { Writer.ExitBlock(); } |
| 136 | |
| 137 | void PGOCtxProfileWriter::writeContextual(const ContextNode &RootNode, |
| 138 | const ContextNode *Unhandled, |
| 139 | uint64_t TotalRootEntryCount) { |
| 140 | if (!IncludeEmpty && (!TotalRootEntryCount || (RootNode.counters_size() > 0 && |
| 141 | RootNode.entrycount() == 0))) |
| 142 | return; |
| 143 | Writer.EnterSubblock(BlockID: PGOCtxProfileBlockIDs::ContextRootBlockID, CodeLen); |
| 144 | writeGuid(Guid: RootNode.guid()); |
| 145 | writeRootEntryCount(TotalRootEntryCount); |
| 146 | writeCounters(Counters: {RootNode.counters(), RootNode.counters_size()}); |
| 147 | |
| 148 | Writer.EnterSubblock(BlockID: PGOCtxProfileBlockIDs::UnhandledBlockID, CodeLen); |
| 149 | for (const auto *P = Unhandled; P; P = P->next()) |
| 150 | writeFlat(Guid: P->guid(), Buffer: P->counters(), BufferSize: P->counters_size()); |
| 151 | Writer.ExitBlock(); |
| 152 | |
| 153 | writeSubcontexts(Node: RootNode); |
| 154 | Writer.ExitBlock(); |
| 155 | } |
| 156 | |
| 157 | void PGOCtxProfileWriter::writeFlat(ctx_profile::GUID Guid, |
| 158 | const uint64_t *Buffer, size_t Size) { |
| 159 | Writer.EnterSubblock(BlockID: PGOCtxProfileBlockIDs::FlatProfileBlockID, CodeLen); |
| 160 | writeGuid(Guid); |
| 161 | writeCounters(Counters: {Buffer, Size}); |
| 162 | Writer.ExitBlock(); |
| 163 | } |
| 164 | |
| 165 | namespace { |
| 166 | |
| 167 | /// Representation of the context node suitable for yaml serialization / |
| 168 | /// deserialization. |
| 169 | using SerializableFlatProfileRepresentation = |
| 170 | std::pair<ctx_profile::GUID, std::vector<uint64_t>>; |
| 171 | |
| 172 | struct SerializableCtxRepresentation { |
| 173 | ctx_profile::GUID Guid = 0; |
| 174 | std::vector<uint64_t> Counters; |
| 175 | std::vector<std::vector<SerializableCtxRepresentation>> Callsites; |
| 176 | }; |
| 177 | |
| 178 | struct SerializableRootRepresentation : public SerializableCtxRepresentation { |
| 179 | uint64_t TotalRootEntryCount = 0; |
| 180 | std::vector<SerializableFlatProfileRepresentation> Unhandled; |
| 181 | }; |
| 182 | |
| 183 | struct SerializableProfileRepresentation { |
| 184 | std::vector<SerializableRootRepresentation> Contexts; |
| 185 | std::vector<SerializableFlatProfileRepresentation> FlatProfiles; |
| 186 | }; |
| 187 | |
| 188 | ctx_profile::ContextNode * |
| 189 | createNode(std::vector<std::unique_ptr<char[]>> &Nodes, |
| 190 | const std::vector<SerializableCtxRepresentation> &DCList); |
| 191 | |
| 192 | // Convert a DeserializableCtx into a ContextNode, potentially linking it to |
| 193 | // its sibling (e.g. callee at same callsite) "Next". |
| 194 | ctx_profile::ContextNode * |
| 195 | createNode(std::vector<std::unique_ptr<char[]>> &Nodes, |
| 196 | const SerializableCtxRepresentation &DC, |
| 197 | ctx_profile::ContextNode *Next = nullptr) { |
| 198 | auto AllocSize = ctx_profile::ContextNode::getAllocSize(NumCounters: DC.Counters.size(), |
| 199 | NumCallsites: DC.Callsites.size()); |
| 200 | auto *Mem = Nodes.emplace_back(args: std::make_unique<char[]>(num: AllocSize)).get(); |
| 201 | std::memset(s: Mem, c: 0, n: AllocSize); |
| 202 | auto *Ret = new (Mem) ctx_profile::ContextNode(DC.Guid, DC.Counters.size(), |
| 203 | DC.Callsites.size(), Next); |
| 204 | std::memcpy(dest: Ret->counters(), src: DC.Counters.data(), |
| 205 | n: sizeof(uint64_t) * DC.Counters.size()); |
| 206 | for (const auto &[I, DCList] : llvm::enumerate(First: DC.Callsites)) |
| 207 | Ret->subContexts()[I] = createNode(Nodes, DCList); |
| 208 | return Ret; |
| 209 | } |
| 210 | |
| 211 | // Convert a list of SerializableCtxRepresentation into a linked list of |
| 212 | // ContextNodes. |
| 213 | ctx_profile::ContextNode * |
| 214 | createNode(std::vector<std::unique_ptr<char[]>> &Nodes, |
| 215 | const std::vector<SerializableCtxRepresentation> &DCList) { |
| 216 | ctx_profile::ContextNode *List = nullptr; |
| 217 | for (const auto &DC : DCList) |
| 218 | List = createNode(Nodes, DC, Next: List); |
| 219 | return List; |
| 220 | } |
| 221 | } // namespace |
| 222 | |
| 223 | LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableCtxRepresentation) |
| 224 | LLVM_YAML_IS_SEQUENCE_VECTOR(std::vector<SerializableCtxRepresentation>) |
| 225 | LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableRootRepresentation) |
| 226 | LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableFlatProfileRepresentation) |
| 227 | template <> struct yaml::MappingTraits<SerializableCtxRepresentation> { |
| 228 | static void mapping(yaml::IO &IO, SerializableCtxRepresentation &SCR) { |
| 229 | IO.mapRequired(Key: "Guid" , Val&: SCR.Guid); |
| 230 | IO.mapRequired(Key: "Counters" , Val&: SCR.Counters); |
| 231 | IO.mapOptional(Key: "Callsites" , Val&: SCR.Callsites); |
| 232 | } |
| 233 | }; |
| 234 | |
| 235 | template <> struct yaml::MappingTraits<SerializableRootRepresentation> { |
| 236 | static void mapping(yaml::IO &IO, SerializableRootRepresentation &R) { |
| 237 | yaml::MappingTraits<SerializableCtxRepresentation>::mapping(IO, SCR&: R); |
| 238 | IO.mapRequired(Key: "TotalRootEntryCount" , Val&: R.TotalRootEntryCount); |
| 239 | IO.mapOptional(Key: "Unhandled" , Val&: R.Unhandled); |
| 240 | } |
| 241 | }; |
| 242 | |
| 243 | template <> struct yaml::MappingTraits<SerializableProfileRepresentation> { |
| 244 | static void mapping(yaml::IO &IO, SerializableProfileRepresentation &SPR) { |
| 245 | IO.mapOptional(Key: "Contexts" , Val&: SPR.Contexts); |
| 246 | IO.mapOptional(Key: "FlatProfiles" , Val&: SPR.FlatProfiles); |
| 247 | } |
| 248 | }; |
| 249 | |
| 250 | template <> struct yaml::MappingTraits<SerializableFlatProfileRepresentation> { |
| 251 | static void mapping(yaml::IO &IO, |
| 252 | SerializableFlatProfileRepresentation &SFPR) { |
| 253 | IO.mapRequired(Key: "Guid" , Val&: SFPR.first); |
| 254 | IO.mapRequired(Key: "Counters" , Val&: SFPR.second); |
| 255 | } |
| 256 | }; |
| 257 | |
| 258 | Error llvm::createCtxProfFromYAML(StringRef Profile, raw_ostream &Out) { |
| 259 | yaml::Input In(Profile); |
| 260 | SerializableProfileRepresentation SPR; |
| 261 | In >> SPR; |
| 262 | if (In.error()) |
| 263 | return createStringError(EC: In.error(), S: "incorrect yaml content" ); |
| 264 | std::vector<std::unique_ptr<char[]>> Nodes; |
| 265 | std::error_code EC; |
| 266 | if (EC) |
| 267 | return createStringError(EC, S: "failed to open output" ); |
| 268 | PGOCtxProfileWriter Writer(Out); |
| 269 | |
| 270 | if (!SPR.Contexts.empty()) { |
| 271 | Writer.startContextSection(); |
| 272 | for (const auto &DC : SPR.Contexts) { |
| 273 | auto *TopList = createNode(Nodes, DC); |
| 274 | if (!TopList) |
| 275 | return createStringError( |
| 276 | Fmt: "Unexpected error converting internal structure to ctx profile" ); |
| 277 | |
| 278 | ctx_profile::ContextNode *FirstUnhandled = nullptr; |
| 279 | for (const auto &U : DC.Unhandled) { |
| 280 | SerializableCtxRepresentation Unhandled; |
| 281 | Unhandled.Guid = U.first; |
| 282 | Unhandled.Counters = U.second; |
| 283 | FirstUnhandled = createNode(Nodes, DC: Unhandled, Next: FirstUnhandled); |
| 284 | } |
| 285 | Writer.writeContextual(RootNode: *TopList, Unhandled: FirstUnhandled, TotalRootEntryCount: DC.TotalRootEntryCount); |
| 286 | } |
| 287 | Writer.endContextSection(); |
| 288 | } |
| 289 | if (!SPR.FlatProfiles.empty()) { |
| 290 | Writer.startFlatSection(); |
| 291 | for (const auto &[Guid, Counters] : SPR.FlatProfiles) |
| 292 | Writer.writeFlat(Guid, Buffer: Counters.data(), Size: Counters.size()); |
| 293 | Writer.endFlatSection(); |
| 294 | } |
| 295 | if (EC) |
| 296 | return createStringError(EC, S: "failed to write output" ); |
| 297 | return Error::success(); |
| 298 | } |
| 299 | |