| //===- PGOCtxProfWriter.cpp - Contextual Instrumentation profile writer ---===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Write a contextual profile to bitstream. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ProfileData/PGOCtxProfWriter.h" | |
| #include "llvm/Bitstream/BitCodeEnums.h" | |
| #include "llvm/ProfileData/CtxInstrContextNode.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/YAMLTraits.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| using namespace llvm; | |
| using namespace llvm::ctx_profile; | |
| static cl::opt<bool> | |
| IncludeEmptyOpt("ctx-prof-include-empty", cl::init(false), | |
| cl::desc("Also write profiles with all-zero counters. " | |
| "Intended for testing/debugging.")); | |
| PGOCtxProfileWriter::PGOCtxProfileWriter( | |
| raw_ostream &Out, std::optional<unsigned> VersionOverride, | |
| bool IncludeEmpty) | |
| : Writer(Out, 0), | |
| IncludeEmpty(IncludeEmptyOpt.getNumOccurrences() > 0 ? IncludeEmptyOpt | |
| : IncludeEmpty) { | |
| static_assert(ContainerMagic.size() == 4); | |
| Out.write(ContainerMagic.data(), ContainerMagic.size()); | |
| Writer.EnterBlockInfoBlock(); | |
| { | |
| auto DescribeBlock = [&](unsigned ID, StringRef Name) { | |
| Writer.EmitRecord(bitc::BLOCKINFO_CODE_SETBID, | |
| SmallVector<unsigned, 1>{ID}); | |
| Writer.EmitRecord(bitc::BLOCKINFO_CODE_BLOCKNAME, | |
| llvm::arrayRefFromStringRef(Name)); | |
| }; | |
| SmallVector<uint64_t, 16> Data; | |
| auto DescribeRecord = [&](unsigned RecordID, StringRef Name) { | |
| Data.clear(); | |
| Data.push_back(RecordID); | |
| llvm::append_range(Data, Name); | |
| Writer.EmitRecord(bitc::BLOCKINFO_CODE_SETRECORDNAME, Data); | |
| }; | |
| DescribeBlock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID, "Metadata"); | |
| DescribeRecord(PGOCtxProfileRecords::Version, "Version"); | |
| DescribeBlock(PGOCtxProfileBlockIDs::ContextsSectionBlockID, "Contexts"); | |
| DescribeBlock(PGOCtxProfileBlockIDs::ContextRootBlockID, "Root"); | |
| DescribeRecord(PGOCtxProfileRecords::Guid, "GUID"); | |
| DescribeRecord(PGOCtxProfileRecords::TotalRootEntryCount, | |
| "TotalRootEntryCount"); | |
| DescribeRecord(PGOCtxProfileRecords::Counters, "Counters"); | |
| DescribeBlock(PGOCtxProfileBlockIDs::UnhandledBlockID, "Unhandled"); | |
| DescribeBlock(PGOCtxProfileBlockIDs::ContextNodeBlockID, "Context"); | |
| DescribeRecord(PGOCtxProfileRecords::Guid, "GUID"); | |
| DescribeRecord(PGOCtxProfileRecords::CallsiteIndex, "CalleeIndex"); | |
| DescribeRecord(PGOCtxProfileRecords::Counters, "Counters"); | |
| DescribeBlock(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID, | |
| "FlatProfiles"); | |
| DescribeBlock(PGOCtxProfileBlockIDs::FlatProfileBlockID, "Flat"); | |
| DescribeRecord(PGOCtxProfileRecords::Guid, "GUID"); | |
| DescribeRecord(PGOCtxProfileRecords::Counters, "Counters"); | |
| } | |
| Writer.ExitBlock(); | |
| Writer.EnterSubblock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID, CodeLen); | |
| const auto Version = VersionOverride.value_or(CurrentVersion); | |
| Writer.EmitRecord(PGOCtxProfileRecords::Version, | |
| SmallVector<unsigned, 1>({Version})); | |
| } | |
| void PGOCtxProfileWriter::writeCounters(ArrayRef<uint64_t> Counters) { | |
| Writer.EmitCode(bitc::UNABBREV_RECORD); | |
| Writer.EmitVBR(PGOCtxProfileRecords::Counters, VBREncodingBits); | |
| Writer.EmitVBR(Counters.size(), VBREncodingBits); | |
| for (uint64_t C : Counters) | |
| Writer.EmitVBR64(C, VBREncodingBits); | |
| } | |
| void PGOCtxProfileWriter::writeGuid(ctx_profile::GUID Guid) { | |
| Writer.EmitRecord(PGOCtxProfileRecords::Guid, SmallVector<uint64_t, 1>{Guid}); | |
| } | |
| void PGOCtxProfileWriter::writeCallsiteIndex(uint32_t CallsiteIndex) { | |
| Writer.EmitRecord(PGOCtxProfileRecords::CallsiteIndex, | |
| SmallVector<uint64_t, 1>{CallsiteIndex}); | |
| } | |
| void PGOCtxProfileWriter::writeRootEntryCount(uint64_t TotalRootEntryCount) { | |
| Writer.EmitRecord(PGOCtxProfileRecords::TotalRootEntryCount, | |
| SmallVector<uint64_t, 1>{TotalRootEntryCount}); | |
| } | |
| // recursively write all the subcontexts. We do need to traverse depth first to | |
| // model the context->subcontext implicitly, and since this captures call | |
| // stacks, we don't really need to be worried about stack overflow and we can | |
| // keep the implementation simple. | |
| void PGOCtxProfileWriter::writeNode(uint32_t CallsiteIndex, | |
| const ContextNode &Node) { | |
| // A node with no counters is an error. We don't expect this to happen from | |
| // the runtime, rather, this is interesting for testing the reader. | |
| if (!IncludeEmpty && (Node.counters_size() > 0 && Node.entrycount() == 0)) | |
| return; | |
| Writer.EnterSubblock(PGOCtxProfileBlockIDs::ContextNodeBlockID, CodeLen); | |
| writeGuid(Node.guid()); | |
| writeCallsiteIndex(CallsiteIndex); | |
| writeCounters({Node.counters(), Node.counters_size()}); | |
| writeSubcontexts(Node); | |
| Writer.ExitBlock(); | |
| } | |
| void PGOCtxProfileWriter::writeSubcontexts(const ContextNode &Node) { | |
| for (uint32_t I = 0U; I < Node.callsites_size(); ++I) | |
| for (const auto *Subcontext = Node.subContexts()[I]; Subcontext; | |
| Subcontext = Subcontext->next()) | |
| writeNode(I, *Subcontext); | |
| } | |
| void PGOCtxProfileWriter::startContextSection() { | |
| Writer.EnterSubblock(PGOCtxProfileBlockIDs::ContextsSectionBlockID, CodeLen); | |
| } | |
| void PGOCtxProfileWriter::startFlatSection() { | |
| Writer.EnterSubblock(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID, | |
| CodeLen); | |
| } | |
| void PGOCtxProfileWriter::endContextSection() { Writer.ExitBlock(); } | |
| void PGOCtxProfileWriter::endFlatSection() { Writer.ExitBlock(); } | |
| void PGOCtxProfileWriter::writeContextual(const ContextNode &RootNode, | |
| const ContextNode *Unhandled, | |
| uint64_t TotalRootEntryCount) { | |
| if (!IncludeEmpty && (!TotalRootEntryCount || (RootNode.counters_size() > 0 && | |
| RootNode.entrycount() == 0))) | |
| return; | |
| Writer.EnterSubblock(PGOCtxProfileBlockIDs::ContextRootBlockID, CodeLen); | |
| writeGuid(RootNode.guid()); | |
| writeRootEntryCount(TotalRootEntryCount); | |
| writeCounters({RootNode.counters(), RootNode.counters_size()}); | |
| Writer.EnterSubblock(PGOCtxProfileBlockIDs::UnhandledBlockID, CodeLen); | |
| for (const auto *P = Unhandled; P; P = P->next()) | |
| writeFlat(P->guid(), P->counters(), P->counters_size()); | |
| Writer.ExitBlock(); | |
| writeSubcontexts(RootNode); | |
| Writer.ExitBlock(); | |
| } | |
| void PGOCtxProfileWriter::writeFlat(ctx_profile::GUID Guid, | |
| const uint64_t *Buffer, size_t Size) { | |
| Writer.EnterSubblock(PGOCtxProfileBlockIDs::FlatProfileBlockID, CodeLen); | |
| writeGuid(Guid); | |
| writeCounters({Buffer, Size}); | |
| Writer.ExitBlock(); | |
| } | |
| namespace { | |
| /// Representation of the context node suitable for yaml serialization / | |
| /// deserialization. | |
| using SerializableFlatProfileRepresentation = | |
| std::pair<ctx_profile::GUID, std::vector<uint64_t>>; | |
| struct SerializableCtxRepresentation { | |
| ctx_profile::GUID Guid = 0; | |
| std::vector<uint64_t> Counters; | |
| std::vector<std::vector<SerializableCtxRepresentation>> Callsites; | |
| }; | |
| struct SerializableRootRepresentation : public SerializableCtxRepresentation { | |
| uint64_t TotalRootEntryCount = 0; | |
| std::vector<SerializableFlatProfileRepresentation> Unhandled; | |
| }; | |
| struct SerializableProfileRepresentation { | |
| std::vector<SerializableRootRepresentation> Contexts; | |
| std::vector<SerializableFlatProfileRepresentation> FlatProfiles; | |
| }; | |
| ctx_profile::ContextNode * | |
| createNode(std::vector<std::unique_ptr<char[]>> &Nodes, | |
| const std::vector<SerializableCtxRepresentation> &DCList); | |
| // Convert a DeserializableCtx into a ContextNode, potentially linking it to | |
| // its sibling (e.g. callee at same callsite) "Next". | |
| ctx_profile::ContextNode * | |
| createNode(std::vector<std::unique_ptr<char[]>> &Nodes, | |
| const SerializableCtxRepresentation &DC, | |
| ctx_profile::ContextNode *Next = nullptr) { | |
| auto AllocSize = ctx_profile::ContextNode::getAllocSize(DC.Counters.size(), | |
| DC.Callsites.size()); | |
| auto *Mem = Nodes.emplace_back(std::make_unique<char[]>(AllocSize)).get(); | |
| std::memset(Mem, 0, AllocSize); | |
| auto *Ret = new (Mem) ctx_profile::ContextNode(DC.Guid, DC.Counters.size(), | |
| DC.Callsites.size(), Next); | |
| std::memcpy(Ret->counters(), DC.Counters.data(), | |
| sizeof(uint64_t) * DC.Counters.size()); | |
| for (const auto &[I, DCList] : llvm::enumerate(DC.Callsites)) | |
| Ret->subContexts()[I] = createNode(Nodes, DCList); | |
| return Ret; | |
| } | |
| // Convert a list of SerializableCtxRepresentation into a linked list of | |
| // ContextNodes. | |
| ctx_profile::ContextNode * | |
| createNode(std::vector<std::unique_ptr<char[]>> &Nodes, | |
| const std::vector<SerializableCtxRepresentation> &DCList) { | |
| ctx_profile::ContextNode *List = nullptr; | |
| for (const auto &DC : DCList) | |
| List = createNode(Nodes, DC, List); | |
| return List; | |
| } | |
| } // namespace | |
| LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableCtxRepresentation) | |
| LLVM_YAML_IS_SEQUENCE_VECTOR(std::vector<SerializableCtxRepresentation>) | |
| LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableRootRepresentation) | |
| LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableFlatProfileRepresentation) | |
| template <> struct yaml::MappingTraits<SerializableCtxRepresentation> { | |
| static void mapping(yaml::IO &IO, SerializableCtxRepresentation &SCR) { | |
| IO.mapRequired("Guid", SCR.Guid); | |
| IO.mapRequired("Counters", SCR.Counters); | |
| IO.mapOptional("Callsites", SCR.Callsites); | |
| } | |
| }; | |
| template <> struct yaml::MappingTraits<SerializableRootRepresentation> { | |
| static void mapping(yaml::IO &IO, SerializableRootRepresentation &R) { | |
| yaml::MappingTraits<SerializableCtxRepresentation>::mapping(IO, R); | |
| IO.mapRequired("TotalRootEntryCount", R.TotalRootEntryCount); | |
| IO.mapOptional("Unhandled", R.Unhandled); | |
| } | |
| }; | |
| template <> struct yaml::MappingTraits<SerializableProfileRepresentation> { | |
| static void mapping(yaml::IO &IO, SerializableProfileRepresentation &SPR) { | |
| IO.mapOptional("Contexts", SPR.Contexts); | |
| IO.mapOptional("FlatProfiles", SPR.FlatProfiles); | |
| } | |
| }; | |
| template <> struct yaml::MappingTraits<SerializableFlatProfileRepresentation> { | |
| static void mapping(yaml::IO &IO, | |
| SerializableFlatProfileRepresentation &SFPR) { | |
| IO.mapRequired("Guid", SFPR.first); | |
| IO.mapRequired("Counters", SFPR.second); | |
| } | |
| }; | |
| Error llvm::createCtxProfFromYAML(StringRef Profile, raw_ostream &Out) { | |
| yaml::Input In(Profile); | |
| SerializableProfileRepresentation SPR; | |
| In >> SPR; | |
| if (In.error()) | |
| return createStringError(In.error(), "incorrect yaml content"); | |
| std::vector<std::unique_ptr<char[]>> Nodes; | |
| std::error_code EC; | |
| if (EC) | |
| return createStringError(EC, "failed to open output"); | |
| PGOCtxProfileWriter Writer(Out); | |
| if (!SPR.Contexts.empty()) { | |
| Writer.startContextSection(); | |
| for (const auto &DC : SPR.Contexts) { | |
| auto *TopList = createNode(Nodes, DC); | |
| if (!TopList) | |
| return createStringError( | |
| "Unexpected error converting internal structure to ctx profile"); | |
| ctx_profile::ContextNode *FirstUnhandled = nullptr; | |
| for (const auto &U : DC.Unhandled) { | |
| SerializableCtxRepresentation Unhandled; | |
| Unhandled.Guid = U.first; | |
| Unhandled.Counters = U.second; | |
| FirstUnhandled = createNode(Nodes, Unhandled, FirstUnhandled); | |
| } | |
| Writer.writeContextual(*TopList, FirstUnhandled, DC.TotalRootEntryCount); | |
| } | |
| Writer.endContextSection(); | |
| } | |
| if (!SPR.FlatProfiles.empty()) { | |
| Writer.startFlatSection(); | |
| for (const auto &[Guid, Counters] : SPR.FlatProfiles) | |
| Writer.writeFlat(Guid, Counters.data(), Counters.size()); | |
| Writer.endFlatSection(); | |
| } | |
| if (EC) | |
| return createStringError(EC, "failed to write output"); | |
| return Error::success(); | |
| } |