| //===- SampleProfReader.cpp - Read LLVM sample profile data ---------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements the class that reads LLVM sample profiles. It | |
| // supports three file formats: text, binary and gcov. | |
| // | |
| // The textual representation is useful for debugging and testing purposes. The | |
| // binary representation is more compact, resulting in smaller file sizes. | |
| // | |
| // The gcov encoding is the one generated by GCC's AutoFDO profile creation | |
| // tool (https://github.com/google/autofdo) | |
| // | |
| // All three encodings can be used interchangeably as an input sample profile. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ProfileData/SampleProfReader.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/SmallSet.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/ProfileSummary.h" | |
| #include "llvm/ProfileData/ProfileCommon.h" | |
| #include "llvm/ProfileData/SampleProf.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/Support/Compression.h" | |
| #include "llvm/Support/ErrorOr.h" | |
| #include "llvm/Support/JSON.h" | |
| #include "llvm/Support/LEB128.h" | |
| #include "llvm/Support/LineIterator.h" | |
| #include "llvm/Support/MD5.h" | |
| #include "llvm/Support/MemoryBuffer.h" | |
| #include "llvm/Support/SaveAndRestore.h" | |
| #include "llvm/Support/VirtualFileSystem.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <limits> | |
| #include <memory> | |
| #include <system_error> | |
| #include <vector> | |
| using namespace llvm; | |
| using namespace sampleprof; | |
| #define DEBUG_TYPE "samplepgo-reader" | |
| // This internal option specifies if the profile uses FS discriminators. | |
| // It only applies to text, and binary format profiles. | |
| // For ext-binary format profiles, the flag is set in the summary. | |
| static cl::opt<bool> ProfileIsFSDisciminator( | |
| "profile-isfs", cl::Hidden, cl::init(false), | |
| cl::desc("Profile uses flow sensitive discriminators")); | |
| static cl::opt<bool> | |
| LazyLoadNameTable("sample-profile-lazy-load-name-table", cl::init(true), | |
| cl::Hidden, | |
| cl::desc("Lazy load the name table from the profile.")); | |
| /// Dump the function profile for \p FName. | |
| /// | |
| /// \param FContext Name + context of the function to print. | |
| /// \param OS Stream to emit the output to. | |
| void SampleProfileReader::dumpFunctionProfile(const FunctionSamples &FS, | |
| raw_ostream &OS) { | |
| OS << "Function: " << FS.getContext().toString() << ": " << FS; | |
| } | |
| /// Dump all the function profiles found on stream \p OS. | |
| void SampleProfileReader::dump(raw_ostream &OS) { | |
| std::vector<NameFunctionSamples> V; | |
| sortFuncProfiles(Profiles, V); | |
| for (const auto &I : V) | |
| dumpFunctionProfile(*I.second, OS); | |
| } | |
| static void dumpFunctionProfileJson(const FunctionSamples &S, | |
| json::OStream &JOS, bool TopLevel = false) { | |
| auto DumpBody = [&](const BodySampleMap &BodySamples) { | |
| for (const auto &I : BodySamples) { | |
| const LineLocation &Loc = I.first; | |
| const SampleRecord &Sample = I.second; | |
| JOS.object([&] { | |
| JOS.attribute("line", Loc.LineOffset); | |
| if (Loc.Discriminator) | |
| JOS.attribute("discriminator", Loc.Discriminator); | |
| JOS.attribute("samples", Sample.getSamples()); | |
| auto CallTargets = Sample.getSortedCallTargets(); | |
| if (!CallTargets.empty()) { | |
| JOS.attributeArray("calls", [&] { | |
| for (const auto &J : CallTargets) { | |
| JOS.object([&] { | |
| JOS.attribute("function", J.first.str()); | |
| JOS.attribute("samples", J.second); | |
| }); | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| }; | |
| auto DumpCallsiteSamples = [&](const CallsiteSampleMap &CallsiteSamples) { | |
| for (const auto &I : CallsiteSamples) | |
| for (const auto &FS : I.second) { | |
| const LineLocation &Loc = I.first; | |
| const FunctionSamples &CalleeSamples = FS.second; | |
| JOS.object([&] { | |
| JOS.attribute("line", Loc.LineOffset); | |
| if (Loc.Discriminator) | |
| JOS.attribute("discriminator", Loc.Discriminator); | |
| JOS.attributeArray( | |
| "samples", [&] { dumpFunctionProfileJson(CalleeSamples, JOS); }); | |
| }); | |
| } | |
| }; | |
| JOS.object([&] { | |
| JOS.attribute("name", S.getFunction().str()); | |
| JOS.attribute("total", S.getTotalSamples()); | |
| if (TopLevel) | |
| JOS.attribute("head", S.getHeadSamples()); | |
| const auto &BodySamples = S.getBodySamples(); | |
| if (!BodySamples.empty()) | |
| JOS.attributeArray("body", [&] { DumpBody(BodySamples); }); | |
| const auto &CallsiteSamples = S.getCallsiteSamples(); | |
| if (!CallsiteSamples.empty()) | |
| JOS.attributeArray("callsites", | |
| [&] { DumpCallsiteSamples(CallsiteSamples); }); | |
| }); | |
| } | |
| /// Dump all the function profiles found on stream \p OS in the JSON format. | |
| void SampleProfileReader::dumpJson(raw_ostream &OS) { | |
| std::vector<NameFunctionSamples> V; | |
| sortFuncProfiles(Profiles, V); | |
| json::OStream JOS(OS, 2); | |
| JOS.arrayBegin(); | |
| for (const auto &F : V) | |
| dumpFunctionProfileJson(*F.second, JOS, true); | |
| JOS.arrayEnd(); | |
| // Emit a newline character at the end as json::OStream doesn't emit one. | |
| OS << "\n"; | |
| } | |
| /// Parse \p Input as function head. | |
| /// | |
| /// Parse one line of \p Input, and update function name in \p FName, | |
| /// function's total sample count in \p NumSamples, function's entry | |
| /// count in \p NumHeadSamples. | |
| /// | |
| /// \returns true if parsing is successful. | |
| static bool ParseHead(const StringRef &Input, StringRef &FName, | |
| uint64_t &NumSamples, uint64_t &NumHeadSamples) { | |
| if (Input[0] == ' ') | |
| return false; | |
| size_t n2 = Input.rfind(':'); | |
| size_t n1 = Input.rfind(':', n2 - 1); | |
| FName = Input.substr(0, n1); | |
| if (Input.substr(n1 + 1, n2 - n1 - 1).getAsInteger(10, NumSamples)) | |
| return false; | |
| if (Input.substr(n2 + 1).getAsInteger(10, NumHeadSamples)) | |
| return false; | |
| return true; | |
| } | |
| /// Returns true if line offset \p L is legal (only has 16 bits). | |
| static bool isOffsetLegal(unsigned L) { return (L & 0xffff) == L; } | |
| /// Parse \p Input that contains metadata. | |
| /// Possible metadata: | |
| /// - CFG Checksum information: | |
| /// !CFGChecksum: 12345 | |
| /// - CFG Checksum information: | |
| /// !Attributes: 1 | |
| /// Stores the FunctionHash (a.k.a. CFG Checksum) into \p FunctionHash. | |
| static bool parseMetadata(const StringRef &Input, uint64_t &FunctionHash, | |
| uint32_t &Attributes) { | |
| if (Input.starts_with("!CFGChecksum:")) { | |
| StringRef CFGInfo = Input.substr(strlen("!CFGChecksum:")).trim(); | |
| return !CFGInfo.getAsInteger(10, FunctionHash); | |
| } | |
| if (Input.starts_with("!Attributes:")) { | |
| StringRef Attrib = Input.substr(strlen("!Attributes:")).trim(); | |
| return !Attrib.getAsInteger(10, Attributes); | |
| } | |
| return false; | |
| } | |
| enum class LineType { | |
| CallSiteProfile, | |
| BodyProfile, | |
| Metadata, | |
| VirtualCallTypeProfile, | |
| }; | |
| // Parse `Input` as a white-space separated list of `vtable:count` pairs. An | |
| // example input line is `_ZTVbar:1471 _ZTVfoo:630`. | |
| static bool parseTypeCountMap(StringRef Input, | |
| DenseMap<StringRef, uint64_t> &TypeCountMap) { | |
| for (size_t Index = Input.find_first_not_of(' '); Index != StringRef::npos;) { | |
| size_t ColonIndex = Input.find(':', Index); | |
| if (ColonIndex == StringRef::npos) | |
| return false; // No colon found, invalid format. | |
| StringRef TypeName = Input.substr(Index, ColonIndex - Index); | |
| // CountIndex is the start index of count. | |
| size_t CountStartIndex = ColonIndex + 1; | |
| // NextIndex is the start index after the 'target:count' pair. | |
| size_t NextIndex = Input.find_first_of(' ', CountStartIndex); | |
| uint64_t Count; | |
| if (Input.substr(CountStartIndex, NextIndex - CountStartIndex) | |
| .getAsInteger(10, Count)) | |
| return false; // Invalid count. | |
| // Error on duplicated type names in one line of input. | |
| auto [Iter, Inserted] = TypeCountMap.insert({TypeName, Count}); | |
| if (!Inserted) | |
| return false; | |
| Index = (NextIndex == StringRef::npos) | |
| ? StringRef::npos | |
| : Input.find_first_not_of(' ', NextIndex); | |
| } | |
| return true; | |
| } | |
| /// Parse \p Input as line sample. | |
| /// | |
| /// \param Input input line. | |
| /// \param LineTy Type of this line. | |
| /// \param Depth the depth of the inline stack. | |
| /// \param NumSamples total samples of the line/inlined callsite. | |
| /// \param LineOffset line offset to the start of the function. | |
| /// \param Discriminator discriminator of the line. | |
| /// \param TargetCountMap map from indirect call target to count. | |
| /// \param FunctionHash the function's CFG hash, used by pseudo probe. | |
| /// | |
| /// returns true if parsing is successful. | |
| static bool ParseLine(const StringRef &Input, LineType &LineTy, uint32_t &Depth, | |
| uint64_t &NumSamples, uint32_t &LineOffset, | |
| uint32_t &Discriminator, StringRef &CalleeName, | |
| DenseMap<StringRef, uint64_t> &TargetCountMap, | |
| DenseMap<StringRef, uint64_t> &TypeCountMap, | |
| uint64_t &FunctionHash, uint32_t &Attributes, | |
| bool &IsFlat) { | |
| for (Depth = 0; Input[Depth] == ' '; Depth++) | |
| ; | |
| if (Depth == 0) | |
| return false; | |
| if (Input[Depth] == '!') { | |
| LineTy = LineType::Metadata; | |
| // This metadata is only for manual inspection only. We already created a | |
| // FunctionSamples and put it in the profile map, so there is no point | |
| // to skip profiles even they have no use for ThinLTO. | |
| if (Input == StringRef(" !Flat")) { | |
| IsFlat = true; | |
| return true; | |
| } | |
| return parseMetadata(Input.substr(Depth), FunctionHash, Attributes); | |
| } | |
| size_t n1 = Input.find(':'); | |
| StringRef Loc = Input.substr(Depth, n1 - Depth); | |
| size_t n2 = Loc.find('.'); | |
| if (n2 == StringRef::npos) { | |
| if (Loc.getAsInteger(10, LineOffset) || !isOffsetLegal(LineOffset)) | |
| return false; | |
| Discriminator = 0; | |
| } else { | |
| if (Loc.substr(0, n2).getAsInteger(10, LineOffset)) | |
| return false; | |
| if (Loc.substr(n2 + 1).getAsInteger(10, Discriminator)) | |
| return false; | |
| } | |
| StringRef Rest = Input.substr(n1 + 2); | |
| if (isDigit(Rest[0])) { | |
| LineTy = LineType::BodyProfile; | |
| size_t n3 = Rest.find(' '); | |
| if (n3 == StringRef::npos) { | |
| if (Rest.getAsInteger(10, NumSamples)) | |
| return false; | |
| } else { | |
| if (Rest.substr(0, n3).getAsInteger(10, NumSamples)) | |
| return false; | |
| } | |
| // Find call targets and their sample counts. | |
| // Note: In some cases, there are symbols in the profile which are not | |
| // mangled. To accommodate such cases, use colon + integer pairs as the | |
| // anchor points. | |
| // An example: | |
| // _M_construct<char *>:1000 string_view<std::allocator<char> >:437 | |
| // ":1000" and ":437" are used as anchor points so the string above will | |
| // be interpreted as | |
| // target: _M_construct<char *> | |
| // count: 1000 | |
| // target: string_view<std::allocator<char> > | |
| // count: 437 | |
| while (n3 != StringRef::npos) { | |
| n3 += Rest.substr(n3).find_first_not_of(' '); | |
| Rest = Rest.substr(n3); | |
| n3 = Rest.find_first_of(':'); | |
| if (n3 == StringRef::npos || n3 == 0) | |
| return false; | |
| StringRef Target; | |
| uint64_t count, n4; | |
| while (true) { | |
| // Get the segment after the current colon. | |
| StringRef AfterColon = Rest.substr(n3 + 1); | |
| // Get the target symbol before the current colon. | |
| Target = Rest.substr(0, n3); | |
| // Check if the word after the current colon is an integer. | |
| n4 = AfterColon.find_first_of(' '); | |
| n4 = (n4 != StringRef::npos) ? n3 + n4 + 1 : Rest.size(); | |
| StringRef WordAfterColon = Rest.substr(n3 + 1, n4 - n3 - 1); | |
| if (!WordAfterColon.getAsInteger(10, count)) | |
| break; | |
| // Try to find the next colon. | |
| uint64_t n5 = AfterColon.find_first_of(':'); | |
| if (n5 == StringRef::npos) | |
| return false; | |
| n3 += n5 + 1; | |
| } | |
| // An anchor point is found. Save the {target, count} pair | |
| TargetCountMap[Target] = count; | |
| if (n4 == Rest.size()) | |
| break; | |
| // Change n3 to the next blank space after colon + integer pair. | |
| n3 = n4; | |
| } | |
| } else if (Rest.starts_with(kVTableProfPrefix)) { | |
| LineTy = LineType::VirtualCallTypeProfile; | |
| return parseTypeCountMap(Rest.substr(strlen(kVTableProfPrefix)), | |
| TypeCountMap); | |
| } else { | |
| LineTy = LineType::CallSiteProfile; | |
| size_t n3 = Rest.find_last_of(':'); | |
| CalleeName = Rest.substr(0, n3); | |
| if (Rest.substr(n3 + 1).getAsInteger(10, NumSamples)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /// Load samples from a text file. | |
| /// | |
| /// See the documentation at the top of the file for an explanation of | |
| /// the expected format. | |
| /// | |
| /// \returns true if the file was loaded successfully, false otherwise. | |
| std::error_code SampleProfileReaderText::readImpl() { | |
| line_iterator LineIt(*Buffer, /*SkipBlanks=*/true, '#'); | |
| sampleprof_error Result = sampleprof_error::success; | |
| InlineCallStack InlineStack; | |
| uint32_t TopLevelProbeProfileCount = 0; | |
| // DepthMetadata tracks whether we have processed metadata for the current | |
| // top-level or nested function profile. | |
| uint32_t DepthMetadata = 0; | |
| std::vector<SampleContext *> FlatSamples; | |
| ProfileIsFS = ProfileIsFSDisciminator; | |
| FunctionSamples::ProfileIsFS = ProfileIsFS; | |
| for (; !LineIt.is_at_eof(); ++LineIt) { | |
| size_t pos = LineIt->find_first_not_of(' '); | |
| if (pos == LineIt->npos || (*LineIt)[pos] == '#') | |
| continue; | |
| // Read the header of each function. | |
| // | |
| // Note that for function identifiers we are actually expecting | |
| // mangled names, but we may not always get them. This happens when | |
| // the compiler decides not to emit the function (e.g., it was inlined | |
| // and removed). In this case, the binary will not have the linkage | |
| // name for the function, so the profiler will emit the function's | |
| // unmangled name, which may contain characters like ':' and '>' in its | |
| // name (member functions, templates, etc). | |
| // | |
| // The only requirement we place on the identifier, then, is that it | |
| // should not begin with a number. | |
| if ((*LineIt)[0] != ' ') { | |
| uint64_t NumSamples, NumHeadSamples; | |
| StringRef FName; | |
| if (!ParseHead(*LineIt, FName, NumSamples, NumHeadSamples)) { | |
| reportError(LineIt.line_number(), | |
| "Expected 'mangled_name:NUM:NUM', found " + *LineIt); | |
| return sampleprof_error::malformed; | |
| } | |
| DepthMetadata = 0; | |
| SampleContext FContext(FName, CSNameTable); | |
| if (FContext.hasContext()) | |
| ++CSProfileCount; | |
| FunctionSamples &FProfile = Profiles.create(FContext); | |
| mergeSampleProfErrors(Result, FProfile.addTotalSamples(NumSamples)); | |
| mergeSampleProfErrors(Result, FProfile.addHeadSamples(NumHeadSamples)); | |
| InlineStack.clear(); | |
| InlineStack.push_back(&FProfile); | |
| } else { | |
| uint64_t NumSamples; | |
| StringRef FName; | |
| DenseMap<StringRef, uint64_t> TargetCountMap; | |
| DenseMap<StringRef, uint64_t> TypeCountMap; | |
| uint32_t Depth, LineOffset, Discriminator; | |
| LineType LineTy = LineType::BodyProfile; | |
| uint64_t FunctionHash = 0; | |
| uint32_t Attributes = 0; | |
| bool IsFlat = false; | |
| // TODO: Update ParseLine to return an error code instead of a bool and | |
| // report it. | |
| if (!ParseLine(*LineIt, LineTy, Depth, NumSamples, LineOffset, | |
| Discriminator, FName, TargetCountMap, TypeCountMap, | |
| FunctionHash, Attributes, IsFlat)) { | |
| switch (LineTy) { | |
| case LineType::Metadata: | |
| reportError(LineIt.line_number(), | |
| "Cannot parse metadata: " + *LineIt); | |
| break; | |
| case LineType::VirtualCallTypeProfile: | |
| reportError(LineIt.line_number(), | |
| "Expected 'vtables [mangled_vtable:NUM]+', found " + | |
| *LineIt); | |
| break; | |
| default: | |
| reportError(LineIt.line_number(), | |
| "Expected 'NUM[.NUM]: NUM[ mangled_name:NUM]*', found " + | |
| *LineIt); | |
| } | |
| return sampleprof_error::malformed; | |
| } | |
| if (LineTy != LineType::Metadata && Depth == DepthMetadata) { | |
| // Metadata must be put at the end of a function profile. | |
| reportError(LineIt.line_number(), | |
| "Found non-metadata after metadata: " + *LineIt); | |
| return sampleprof_error::malformed; | |
| } | |
| // Here we handle FS discriminators. | |
| Discriminator &= getDiscriminatorMask(); | |
| while (InlineStack.size() > Depth) { | |
| InlineStack.pop_back(); | |
| } | |
| switch (LineTy) { | |
| case LineType::CallSiteProfile: { | |
| FunctionSamples &FSamples = InlineStack.back()->functionSamplesAt( | |
| LineLocation(LineOffset, Discriminator))[FunctionId(FName)]; | |
| FSamples.setFunction(FunctionId(FName)); | |
| mergeSampleProfErrors(Result, FSamples.addTotalSamples(NumSamples)); | |
| InlineStack.push_back(&FSamples); | |
| DepthMetadata = 0; | |
| break; | |
| } | |
| case LineType::VirtualCallTypeProfile: { | |
| mergeSampleProfErrors( | |
| Result, InlineStack.back()->addCallsiteVTableTypeProfAt( | |
| LineLocation(LineOffset, Discriminator), TypeCountMap)); | |
| break; | |
| } | |
| case LineType::BodyProfile: { | |
| FunctionSamples &FProfile = *InlineStack.back(); | |
| for (const auto &name_count : TargetCountMap) { | |
| mergeSampleProfErrors(Result, FProfile.addCalledTargetSamples( | |
| LineOffset, Discriminator, | |
| FunctionId(name_count.first), | |
| name_count.second)); | |
| } | |
| mergeSampleProfErrors( | |
| Result, | |
| FProfile.addBodySamples(LineOffset, Discriminator, NumSamples)); | |
| break; | |
| } | |
| case LineType::Metadata: { | |
| FunctionSamples &FProfile = *InlineStack.back(); | |
| if (FunctionHash) { | |
| FProfile.setFunctionHash(FunctionHash); | |
| if (Depth == 1) | |
| ++TopLevelProbeProfileCount; | |
| } | |
| FProfile.getContext().setAllAttributes(Attributes); | |
| if (Attributes & (uint32_t)ContextShouldBeInlined) | |
| ProfileIsPreInlined = true; | |
| DepthMetadata = Depth; | |
| if (IsFlat) { | |
| if (Depth == 1) | |
| FlatSamples.push_back(&FProfile.getContext()); | |
| else | |
| Ctx.diagnose(DiagnosticInfoSampleProfile( | |
| Buffer->getBufferIdentifier(), LineIt.line_number(), | |
| "!Flat may only be used at top level function.", DS_Warning)); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Honor the option to skip flat functions. Since they are already added to | |
| // the profile map, remove them all here. | |
| if (SkipFlatProf) | |
| for (SampleContext *FlatSample : FlatSamples) | |
| Profiles.erase(*FlatSample); | |
| assert((CSProfileCount == 0 || CSProfileCount == Profiles.size()) && | |
| "Cannot have both context-sensitive and regular profile"); | |
| ProfileIsCS = (CSProfileCount > 0); | |
| assert((TopLevelProbeProfileCount == 0 || | |
| TopLevelProbeProfileCount == Profiles.size()) && | |
| "Cannot have both probe-based profiles and regular profiles"); | |
| ProfileIsProbeBased = (TopLevelProbeProfileCount > 0); | |
| FunctionSamples::ProfileIsProbeBased = ProfileIsProbeBased; | |
| FunctionSamples::ProfileIsCS = ProfileIsCS; | |
| FunctionSamples::ProfileIsPreInlined = ProfileIsPreInlined; | |
| if (Result == sampleprof_error::success) | |
| computeSummary(); | |
| return Result; | |
| } | |
| bool SampleProfileReaderText::hasFormat(const MemoryBuffer &Buffer) { | |
| bool result = false; | |
| // Check that the first non-comment line is a valid function header. | |
| line_iterator LineIt(Buffer, /*SkipBlanks=*/true, '#'); | |
| if (!LineIt.is_at_eof()) { | |
| if ((*LineIt)[0] != ' ') { | |
| uint64_t NumSamples, NumHeadSamples; | |
| StringRef FName; | |
| result = ParseHead(*LineIt, FName, NumSamples, NumHeadSamples); | |
| } | |
| } | |
| return result; | |
| } | |
| /// Emit a reader diagnostic for \p ProfError and return its error code. | |
| static std::error_code diagnoseReaderError(const SampleProfileReader &Reader, | |
| sampleprof_error ProfError) { | |
| std::error_code EC = ProfError; | |
| Reader.reportError(0, EC.message()); | |
| return EC; | |
| } | |
| template <typename T> ErrorOr<T> SampleProfileReaderBinary::readNumber() { | |
| if (Data >= End) | |
| return diagnoseReaderError(*this, sampleprof_error::truncated); | |
| unsigned NumBytesRead = 0; | |
| ULEB128DecodeError DecodeError = ULEB128DecodeError::None; | |
| uint64_t Val = decodeULEB128(Data, &NumBytesRead, End, nullptr, &DecodeError); | |
| // Preserve the distinction between incomplete input and an invalid value. | |
| switch (DecodeError) { | |
| case ULEB128DecodeError::None: | |
| break; | |
| case ULEB128DecodeError::UnexpectedEnd: | |
| return diagnoseReaderError(*this, sampleprof_error::truncated); | |
| case ULEB128DecodeError::TooBig: | |
| return diagnoseReaderError(*this, sampleprof_error::malformed); | |
| } | |
| if (Val > std::numeric_limits<T>::max()) | |
| return diagnoseReaderError(*this, sampleprof_error::malformed); | |
| Data += NumBytesRead; | |
| return static_cast<T>(Val); | |
| } | |
| ErrorOr<StringRef> SampleProfileReaderBinary::readString() { | |
| if (Data >= End) | |
| return diagnoseReaderError(*this, sampleprof_error::truncated); | |
| const auto *Terminator = static_cast<const uint8_t *>( | |
| std::memchr(Data, 0, static_cast<size_t>(End - Data))); | |
| if (!Terminator) | |
| return diagnoseReaderError(*this, sampleprof_error::truncated); | |
| StringRef Str(reinterpret_cast<const char *>(Data), Terminator - Data); | |
| Data = Terminator + 1; | |
| return Str; | |
| } | |
| template <typename T> | |
| ErrorOr<T> SampleProfileReaderBinary::readUnencodedNumber() { | |
| if (Data > End || static_cast<size_t>(End - Data) < sizeof(T)) | |
| return diagnoseReaderError(*this, sampleprof_error::truncated); | |
| using namespace support; | |
| T Val = endian::readNext<T, llvm::endianness::little>(Data); | |
| return Val; | |
| } | |
| template <typename T> | |
| inline ErrorOr<size_t> SampleProfileReaderBinary::readStringIndex(T &Table) { | |
| auto Idx = readNumber<size_t>(); | |
| if (std::error_code EC = Idx.getError()) | |
| return EC; | |
| if (*Idx >= Table.size()) | |
| return sampleprof_error::truncated_name_table; | |
| return *Idx; | |
| } | |
| ErrorOr<FunctionId> | |
| SampleProfileReaderBinary::readStringFromTable(size_t *RetIdx) { | |
| if (!NameTable) | |
| return sampleprof_error::truncated_name_table; | |
| auto Idx = readStringIndex(*NameTable); | |
| if (std::error_code EC = Idx.getError()) | |
| return EC; | |
| if (RetIdx) | |
| *RetIdx = *Idx; | |
| return (*NameTable)[*Idx]; | |
| } | |
| ErrorOr<SampleContextFrames> | |
| SampleProfileReaderBinary::readContextFromTable(size_t *RetIdx) { | |
| auto ContextIdx = readNumber<size_t>(); | |
| if (std::error_code EC = ContextIdx.getError()) | |
| return EC; | |
| if (*ContextIdx >= CSNameTable.size()) | |
| return sampleprof_error::truncated_name_table; | |
| if (RetIdx) | |
| *RetIdx = *ContextIdx; | |
| return CSNameTable[*ContextIdx]; | |
| } | |
| ErrorOr<std::pair<SampleContext, uint64_t>> | |
| SampleProfileReaderBinary::readSampleContextFromTable() { | |
| SampleContext Context; | |
| size_t Idx; | |
| if (ProfileIsCS) { | |
| auto FContext(readContextFromTable(&Idx)); | |
| if (std::error_code EC = FContext.getError()) | |
| return EC; | |
| Context = SampleContext(*FContext); | |
| } else { | |
| auto FName(readStringFromTable(&Idx)); | |
| if (std::error_code EC = FName.getError()) | |
| return EC; | |
| Context = SampleContext(*FName); | |
| } | |
| // Since MD5SampleContextStart may point to the profile's file data, need to | |
| // make sure it is reading the same value on big endian CPU. | |
| uint64_t Hash = support::endian::read64le(MD5SampleContextStart + Idx); | |
| // Lazy computing of hash value, write back to the table to cache it. Only | |
| // compute the context's hash value if it is being referenced for the first | |
| // time. | |
| if (Hash == 0) { | |
| assert(MD5SampleContextStart == MD5SampleContextTable.data()); | |
| Hash = Context.getHashCode(); | |
| support::endian::write64le(&MD5SampleContextTable[Idx], Hash); | |
| } | |
| return std::make_pair(Context, Hash); | |
| } | |
| std::error_code | |
| SampleProfileReaderBinary::readVTableTypeCountMap(TypeCountMap &M) { | |
| auto NumVTableTypes = readNumber<uint32_t>(); | |
| if (std::error_code EC = NumVTableTypes.getError()) | |
| return EC; | |
| // With FS discriminators, M may already hold the types of another raw | |
| // location that maps to the same masked location (see below), so reserve | |
| // for both the existing and the incoming vtables. | |
| M.reserve(M.size() + *NumVTableTypes); | |
| for (uint32_t I = 0; I < *NumVTableTypes; ++I) { | |
| auto VTableType(readStringFromTable()); | |
| if (std::error_code EC = VTableType.getError()) | |
| return EC; | |
| auto VTableSamples = readNumber<uint64_t>(); | |
| if (std::error_code EC = VTableSamples.getError()) | |
| return EC; | |
| auto [It, Inserted] = M.try_emplace(*VTableType, *VTableSamples); | |
| if (Inserted) | |
| continue; | |
| // Without FS discriminators, the source profile should not have duplicate | |
| // vtable records at the same location. Emit a warning about duplicate | |
| // vtables and continue processing the profile. | |
| if (!ProfileIsFS) { | |
| Ctx.diagnose(DiagnosticInfoSampleProfile( | |
| Buffer->getBufferIdentifier(), 0, | |
| "Duplicate vtable type " + VTableType->str() + | |
| " at the same location. Additional counters will be ignored.", | |
| DS_Warning)); | |
| continue; | |
| } | |
| // With flow-sensitive discriminators enabled in the profiled binary, | |
| // several raw locations of the profiled instruction may map to the same | |
| // masked location. Accumulate vtable counts for the same masked location, | |
| // like body and callsite samples are accumulated. | |
| It->second = SaturatingAdd(It->second, *VTableSamples); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderBinary::readCallsiteVTableProf(FunctionSamples &FProfile) { | |
| assert(ReadVTableProf && | |
| "Cannot read vtable profiles if ReadVTableProf is false"); | |
| // Read the vtable type profile for the callsite. | |
| auto NumCallsites = readNumber<uint32_t>(); | |
| if (std::error_code EC = NumCallsites.getError()) | |
| return EC; | |
| FProfile.reserveCallsiteTypeCounts(*NumCallsites); | |
| for (uint32_t I = 0; I < *NumCallsites; ++I) { | |
| auto LineOffset = readNumber<uint64_t>(); | |
| if (std::error_code EC = LineOffset.getError()) | |
| return EC; | |
| if (!isOffsetLegal(*LineOffset)) | |
| return sampleprof_error::illegal_line_offset; | |
| auto Discriminator = readNumber<uint64_t>(); | |
| if (std::error_code EC = Discriminator.getError()) | |
| return EC; | |
| // Here we handle FS discriminators: | |
| const uint32_t DiscriminatorVal = (*Discriminator) & getDiscriminatorMask(); | |
| if (std::error_code EC = readVTableTypeCountMap(FProfile.getTypeSamplesAt( | |
| LineLocation(*LineOffset, DiscriminatorVal)))) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile, | |
| bool IsNested) { | |
| if (ProfileSecRange.IsComposite && !IsNested) { | |
| auto NumHeadSamples = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumHeadSamples.getError()) | |
| return EC; | |
| FProfile.addHeadSamples(*NumHeadSamples); | |
| } | |
| auto NumSamples = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumSamples.getError()) | |
| return EC; | |
| FProfile.addTotalSamples(*NumSamples); | |
| // Read the samples in the body. | |
| auto NumRecords = readNumber<uint32_t>(); | |
| if (std::error_code EC = NumRecords.getError()) | |
| return EC; | |
| FProfile.reserveBodySamples(*NumRecords); | |
| for (uint32_t I = 0; I < *NumRecords; ++I) { | |
| auto LineOffset = readNumber<uint64_t>(); | |
| if (std::error_code EC = LineOffset.getError()) | |
| return EC; | |
| if (!isOffsetLegal(*LineOffset)) { | |
| return sampleprof_error::illegal_line_offset; | |
| } | |
| auto Discriminator = readNumber<uint64_t>(); | |
| if (std::error_code EC = Discriminator.getError()) | |
| return EC; | |
| auto NumSamples = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumSamples.getError()) | |
| return EC; | |
| auto NumCalls = readNumber<uint32_t>(); | |
| if (std::error_code EC = NumCalls.getError()) | |
| return EC; | |
| // Here we handle FS discriminators: | |
| uint32_t DiscriminatorVal = (*Discriminator) & getDiscriminatorMask(); | |
| for (uint32_t J = 0; J < *NumCalls; ++J) { | |
| auto CalledFunction(readStringFromTable()); | |
| if (std::error_code EC = CalledFunction.getError()) | |
| return EC; | |
| auto CalledFunctionSamples = readNumber<uint64_t>(); | |
| if (std::error_code EC = CalledFunctionSamples.getError()) | |
| return EC; | |
| FProfile.addCalledTargetSamples(*LineOffset, DiscriminatorVal, | |
| *CalledFunction, *CalledFunctionSamples); | |
| } | |
| FProfile.addBodySamples(*LineOffset, DiscriminatorVal, *NumSamples); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderBinary::readCompositeProfile(FunctionSamples &FProfile, | |
| bool IsNested) { | |
| // Read the number of profile types. | |
| auto ProfNum = readNumber<uint64_t>(); | |
| if (std::error_code EC = ProfNum.getError()) | |
| return EC; | |
| if (ProfileTypeInfoOS) | |
| *ProfileTypeInfoOS << (IsNested ? "Nested function: " : "Function: ") | |
| << FProfile.getContext().toString() | |
| << "\n Profile blocks: " << *ProfNum << "\n"; | |
| // Each type identifies one logical payload for the function. Decoding the | |
| // same type twice would merge absolute counters from malformed input. | |
| SmallSet<uint64_t, 4> SeenTypes; | |
| // Read the specified number of composite profiles. | |
| for (uint64_t I = 0; I < *ProfNum; ++I) { | |
| auto Type = readNumber<uint64_t>(); | |
| if (std::error_code EC = Type.getError()) | |
| return EC; | |
| // Report the conflicting ID so malformed profiles can be diagnosed | |
| // without inspecting their binary encoding. | |
| if (!SeenTypes.insert(*Type).second) { | |
| reportError(0, "Duplicate profile type ID: " + Twine(*Type)); | |
| return sampleprof_error::malformed; | |
| } | |
| auto Size = readNumber<uint64_t>(); | |
| if (std::error_code EC = Size.getError()) | |
| return EC; | |
| if (ProfileTypeInfoOS) | |
| *ProfileTypeInfoOS << " Type: " << *Type << " (" | |
| << getProfTypeName(*Type) | |
| << "), Payload size: " << *Size << "\n"; | |
| const uint64_t RemainingSize = End - Data; | |
| // Diagnose a size that would let the payload cross its containing section. | |
| if (*Size > RemainingSize) { | |
| reportError(0, "Profile type ID " + Twine(*Type) + | |
| " declares payload size " + Twine(*Size) + | |
| ", but only " + Twine(RemainingSize) + | |
| " bytes remain"); | |
| return sampleprof_error::truncated; | |
| } | |
| const uint8_t *PayloadEnd = Data + *Size; | |
| std::error_code EC = sampleprof_error::success; | |
| // Restrict field readers to the current payload so they reject fields that | |
| // extend into the following payload. | |
| SaveAndRestore<const uint8_t *> RestoreEnd(End, PayloadEnd); | |
| switch (*Type) { | |
| case ProfTypeLBR: | |
| EC = readLBRProfile(FProfile, IsNested); | |
| break; | |
| default: | |
| // Skip unknown profile types for forward compatibility. | |
| HasUnknownProfileTypes = true; | |
| Data = PayloadEnd; | |
| break; | |
| } | |
| if (EC) | |
| return EC; | |
| // Reject trailing bytes because every known decoder must consume exactly | |
| // the payload declared for its type. | |
| if (Data != PayloadEnd) { | |
| reportError(0, | |
| "Profile type ID " + Twine(*Type) + | |
| " did not consume its complete payload; unread bytes: " + | |
| Twine(PayloadEnd - Data)); | |
| return sampleprof_error::malformed; | |
| } | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile, | |
| bool IsNested) { | |
| if (ProfileSecRange.IsComposite) { | |
| if (std::error_code EC = readCompositeProfile(FProfile, IsNested)) | |
| return EC; | |
| } else { | |
| if (std::error_code EC = readLBRProfile(FProfile, IsNested)) | |
| return EC; | |
| } | |
| // Read all the samples for inlined function calls. | |
| auto NumCallsites = readNumber<uint32_t>(); | |
| if (std::error_code EC = NumCallsites.getError()) | |
| return EC; | |
| for (uint32_t J = 0; J < *NumCallsites; ++J) { | |
| auto LineOffset = readNumber<uint64_t>(); | |
| if (std::error_code EC = LineOffset.getError()) | |
| return EC; | |
| auto Discriminator = readNumber<uint64_t>(); | |
| if (std::error_code EC = Discriminator.getError()) | |
| return EC; | |
| auto FName(readStringFromTable()); | |
| if (std::error_code EC = FName.getError()) | |
| return EC; | |
| // Here we handle FS discriminators: | |
| uint32_t DiscriminatorVal = (*Discriminator) & getDiscriminatorMask(); | |
| FunctionSamples &CalleeProfile = FProfile.functionSamplesAt( | |
| LineLocation(*LineOffset, DiscriminatorVal))[*FName]; | |
| CalleeProfile.setFunction(*FName); | |
| if (std::error_code EC = readProfile(CalleeProfile, /*IsNested=*/true)) | |
| return EC; | |
| } | |
| if (ReadVTableProf) | |
| return readCallsiteVTableProf(FProfile); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start, | |
| SampleProfileMap &Profiles) { | |
| Data = Start; | |
| ErrorOr<uint64_t> NumHeadSamples = 0; | |
| if (!ProfileSecRange.IsComposite) { | |
| NumHeadSamples = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumHeadSamples.getError()) | |
| return EC; | |
| } | |
| auto FContextHash(readSampleContextFromTable()); | |
| if (std::error_code EC = FContextHash.getError()) | |
| return EC; | |
| auto &[FContext, Hash] = *FContextHash; | |
| // Use the cached hash value for insertion instead of recalculating it. | |
| auto Res = Profiles.try_emplace(Hash, FContext, FunctionSamples()); | |
| FunctionSamples &FProfile = Res.first->second; | |
| FProfile.setContext(FContext); | |
| if (!ProfileSecRange.IsComposite) | |
| FProfile.addHeadSamples(*NumHeadSamples); | |
| if (FContext.hasContext()) | |
| CSProfileCount++; | |
| if (std::error_code EC = readProfile(FProfile, /*IsNested=*/false)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start) { | |
| return readFuncProfile(Start, Profiles); | |
| } | |
| std::error_code SampleProfileReaderBinary::readImpl() { | |
| ProfileIsFS = ProfileIsFSDisciminator; | |
| FunctionSamples::ProfileIsFS = ProfileIsFS; | |
| while (Data < End) { | |
| if (std::error_code EC = readFuncProfile(Data)) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readOneSection( | |
| const uint8_t *Start, uint64_t Size, const SecHdrTableEntry &Entry) { | |
| Data = Start; | |
| End = Start + Size; | |
| switch (Entry.Type) { | |
| case SecProfSummary: | |
| if (std::error_code EC = readSummary()) | |
| return EC; | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagPartial)) | |
| Summary->setPartialProfile(true); | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFullContext)) | |
| FunctionSamples::ProfileIsCS = ProfileIsCS = true; | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagIsPreInlined)) | |
| FunctionSamples::ProfileIsPreInlined = ProfileIsPreInlined = true; | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFSDiscriminator)) | |
| FunctionSamples::ProfileIsFS = ProfileIsFS = true; | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagHasVTableTypeProf)) | |
| ReadVTableProf = true; | |
| break; | |
| case SecNameTable: { | |
| bool FixedLengthMD5 = | |
| hasSecFlag(Entry, SecNameTableFlags::SecFlagFixedLengthMD5); | |
| bool UseMD5 = hasSecFlag(Entry, SecNameTableFlags::SecFlagMD5Name); | |
| // UseMD5 means if THIS section uses MD5, ProfileIsMD5 means if the entire | |
| // profile uses MD5 for function name matching in IPO passes. | |
| ProfileIsMD5 = ProfileIsMD5 || UseMD5; | |
| FunctionSamples::HasUniqSuffix = | |
| hasSecFlag(Entry, SecNameTableFlags::SecFlagUniqSuffix); | |
| bool IsEytzinger = hasSecFlag(Entry, SecNameTableFlags::SecFlagEytzinger); | |
| if (std::error_code EC = | |
| readNameTableSec(UseMD5, FixedLengthMD5, IsEytzinger)) | |
| return EC; | |
| break; | |
| } | |
| case SecCSNameTable: { | |
| if (std::error_code EC = readCSNameTableSec()) | |
| return EC; | |
| break; | |
| } | |
| case SecLBRProfile: | |
| case SecCompositeProfile: | |
| // Retain the section and its encoding for subsequent on-demand reads. | |
| ProfileSecRange = {Data, End, Entry.Type == SecCompositeProfile}; | |
| if (std::error_code EC = readFuncProfiles()) | |
| return EC; | |
| break; | |
| case SecFuncOffsetTable: | |
| case SecCompositeFuncOffsetTable: | |
| // If module is absent, we are using LLVM tools, and need to read all | |
| // profiles, so skip reading the function offset table. | |
| if (!M) { | |
| Data = End; | |
| } else { | |
| bool IsEytzinger = | |
| hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagEytzinger); | |
| bool IsFlat = hasSecFlag(Entry, SecCommonFlags::SecFlagFlat); | |
| // An unflagged function offset table inherently indexes the primary | |
| // Nested symbol span. | |
| bool IsNested = !IsFlat; | |
| assert((!ProfileIsCS || | |
| hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagOrdered) || | |
| IsEytzinger) && | |
| "func offset table should always be sorted or in Eytzinger BFS " | |
| "order in CS profile"); | |
| if (std::error_code EC = readFuncOffsetTable(IsEytzinger, IsNested)) | |
| return EC; | |
| } | |
| break; | |
| case SecFuncMetadata: { | |
| ProfileIsProbeBased = | |
| hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagIsProbeBased); | |
| FunctionSamples::ProfileIsProbeBased = ProfileIsProbeBased; | |
| ProfileHasAttribute = | |
| hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagHasAttribute); | |
| if (std::error_code EC = readFuncMetadata()) | |
| return EC; | |
| break; | |
| } | |
| case SecProfileSymbolList: | |
| if (std::error_code EC = readProfileSymbolList( | |
| hasSecFlag(Entry, SecProfileSymbolListFlags::SecFlagMD5))) | |
| return EC; | |
| break; | |
| default: | |
| if (std::error_code EC = readCustomSection(Entry)) | |
| return EC; | |
| break; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| bool SampleProfileReaderExtBinaryBase::useFuncOffsetList() const { | |
| // If profile is CS, the function offset section is expected to consist of | |
| // sequences of contexts in pre-order layout | |
| // (e.g. [A, A:1 @ B, A:1 @ B:2.3 @ C] [D, D:1 @ E]), so that when a matched | |
| // context in the module is found, the profiles of all its callees are | |
| // recursively loaded. A list is needed since the order of profiles matters. | |
| if (ProfileIsCS) | |
| return true; | |
| // If the profile is MD5, use the map container to lookup functions in | |
| // the module. A remapper has no use on MD5 names. | |
| if (useMD5()) | |
| return false; | |
| // Profile is not MD5 and if a remapper is present, the remapped name of | |
| // every function needed to be matched against the module, so use the list | |
| // container since each entry is accessed. | |
| if (Remapper) | |
| return true; | |
| // Otherwise use the map container for faster lookup. | |
| // TODO: If the cardinality of the function offset section is much smaller | |
| // than the number of functions in the module, using the list container can | |
| // be always faster, but we need to figure out the constant factor to | |
| // determine the cutoff. | |
| return false; | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::read(const DenseSet<StringRef> &FuncsToUse, | |
| SampleProfileMap &Profiles) { | |
| if (FuncsToUse.empty()) | |
| return sampleprof_error::success; | |
| Data = ProfileSecRange.Start; | |
| End = ProfileSecRange.End; | |
| if (std::error_code EC = readFuncProfiles(FuncsToUse, Profiles)) | |
| return EC; | |
| End = Data; | |
| DenseSet<FunctionSamples *> ProfilesToReadMetadata; | |
| for (auto FName : FuncsToUse) { | |
| auto I = Profiles.find(FName); | |
| if (I != Profiles.end()) | |
| ProfilesToReadMetadata.insert(&I->second); | |
| } | |
| if (std::error_code EC = readFuncMetadata(ProfilesToReadMetadata)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| bool SampleProfileReaderExtBinaryBase::collectFuncsFromModule() { | |
| if (!M) | |
| return false; | |
| FuncsToUse.clear(); | |
| for (auto &F : *M) | |
| FuncsToUse.insert(FunctionSamples::getCanonicalFnName(F)); | |
| return true; | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::readFuncOffsetTable(bool IsEytzinger, | |
| bool IsNested) { | |
| if (IsEytzinger) | |
| return readEytzingerFuncOffsetTable(IsNested); | |
| return readLegacyFuncOffsetTable(); | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::readEytzingerFuncOffsetTable(bool IsNested) { | |
| // If there are more than one function offset section, the profile associated | |
| // with the previous section has to be done reading before next one is read. | |
| FuncOffsetTable.reset(); | |
| size_t Size = End - Data; | |
| size_t SpanSize = NameTable->getEytzingerSpan(IsNested).size(); | |
| if (Size != SpanSize * sizeof(uint32_t)) | |
| return sampleprof_error::malformed; | |
| auto *Array = reinterpret_cast<const support::ulittle32_t *>(Data); | |
| ArrayRef<support::ulittle32_t> Offsets(Array, SpanSize); | |
| FuncOffsetTable.emplace(EytzingerMode, NameTable->getEytzingerSpan(IsNested), | |
| Offsets); | |
| Data = End; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readLegacyFuncOffsetTable() { | |
| // If there are more than one function offset section, the profile associated | |
| // with the previous section has to be done reading before next one is read. | |
| FuncOffsetTable.reset(); | |
| FuncOffsetList.clear(); | |
| auto Size = readNumber<uint64_t>(); | |
| if (std::error_code EC = Size.getError()) | |
| return EC; | |
| bool UseFuncOffsetList = useFuncOffsetList(); | |
| if (UseFuncOffsetList) | |
| FuncOffsetList.reserve(*Size); | |
| else | |
| FuncOffsetTable.emplace(InMemoryMode, *Size); | |
| for (uint64_t I = 0; I < *Size; ++I) { | |
| auto FContextHash(readSampleContextFromTable()); | |
| if (std::error_code EC = FContextHash.getError()) | |
| return EC; | |
| auto &[FContext, Hash] = *FContextHash; | |
| auto Offset = readNumber<uint64_t>(); | |
| if (std::error_code EC = Offset.getError()) | |
| return EC; | |
| if (UseFuncOffsetList) | |
| FuncOffsetList.emplace_back(FContext, *Offset); | |
| else | |
| // Because Porfiles replace existing value with new value if collision | |
| // happens, we also use the latest offset so that they are consistent. | |
| FuncOffsetTable->insert(Hash, *Offset); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles( | |
| const DenseSet<StringRef> &FuncsToUse, SampleProfileMap &Profiles) { | |
| const uint8_t *Start = Data; | |
| if (Remapper) { | |
| for (auto Name : FuncsToUse) { | |
| Remapper->insert(Name); | |
| } | |
| } | |
| if (FuncOffsetTable && FuncOffsetTable->isEytzinger() && | |
| useFuncOffsetList()) { | |
| ArrayRef<support::ulittle32_t> Offsets = FuncOffsetTable->getFuncOffsets(); | |
| if (Offsets.size() != FuncOffsetTable->getExpectedSize()) | |
| return sampleprof_error::malformed; | |
| for (const auto &[LocalIdx, RelOffset] : llvm::enumerate(Offsets)) { | |
| if (RelOffset == UINT32_MAX) | |
| continue; | |
| const uint8_t *FuncProfileAddr = Start + RelOffset; | |
| if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles)) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| if (ProfileIsCS) { | |
| assert(useFuncOffsetList()); | |
| DenseSet<uint64_t> FuncGuidsToUse; | |
| if (useMD5()) { | |
| for (auto Name : FuncsToUse) | |
| FuncGuidsToUse.insert(Function::getGUIDAssumingExternalLinkage(Name)); | |
| } | |
| // For each function in current module, load all context profiles for | |
| // the function as well as their callee contexts which can help profile | |
| // guided importing for ThinLTO. This can be achieved by walking | |
| // through an ordered context container, where contexts are laid out | |
| // as if they were walked in preorder of a context trie. While | |
| // traversing the trie, a link to the highest common ancestor node is | |
| // kept so that all of its decendants will be loaded. | |
| const SampleContext *CommonContext = nullptr; | |
| for (const auto &NameOffset : FuncOffsetList) { | |
| const auto &FContext = NameOffset.first; | |
| FunctionId FName = FContext.getFunction(); | |
| StringRef FNameString; | |
| if (!useMD5()) | |
| FNameString = FName.stringRef(); | |
| // For function in the current module, keep its farthest ancestor | |
| // context. This can be used to load itself and its child and | |
| // sibling contexts. | |
| if ((useMD5() && FuncGuidsToUse.count(FName.getHashCode())) || | |
| (!useMD5() && (FuncsToUse.count(FNameString) || | |
| (Remapper && Remapper->exist(FNameString))))) { | |
| if (!CommonContext || !CommonContext->isPrefixOf(FContext)) | |
| CommonContext = &FContext; | |
| } | |
| if (CommonContext == &FContext || | |
| (CommonContext && CommonContext->isPrefixOf(FContext))) { | |
| // Load profile for the current context which originated from | |
| // the common ancestor. | |
| const uint8_t *FuncProfileAddr = Start + NameOffset.second; | |
| if (std::error_code EC = readFuncProfile(FuncProfileAddr)) | |
| return EC; | |
| } | |
| } | |
| } else if (useMD5()) { | |
| assert(!useFuncOffsetList()); | |
| for (auto Name : FuncsToUse) { | |
| auto GUID = MD5Hash(Name); | |
| if (auto Offset = FuncOffsetTable->lookup(GUID)) { | |
| const uint8_t *FuncProfileAddr = Start + *Offset; | |
| if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles)) | |
| return EC; | |
| } | |
| } | |
| } else if (Remapper) { | |
| assert(useFuncOffsetList()); | |
| for (auto NameOffset : FuncOffsetList) { | |
| SampleContext FContext(NameOffset.first); | |
| auto FuncName = FContext.getFunction(); | |
| StringRef FuncNameStr = FuncName.stringRef(); | |
| if (!FuncsToUse.count(FuncNameStr) && !Remapper->exist(FuncNameStr)) | |
| continue; | |
| const uint8_t *FuncProfileAddr = Start + NameOffset.second; | |
| if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles)) | |
| return EC; | |
| } | |
| } else { | |
| assert(!useFuncOffsetList()); | |
| for (auto Name : FuncsToUse) { | |
| if (auto Offset = FuncOffsetTable->lookup(MD5Hash(Name))) { | |
| const uint8_t *FuncProfileAddr = Start + *Offset; | |
| if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles)) | |
| return EC; | |
| } | |
| } | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles() { | |
| // Collect functions used by current module if the Reader has been | |
| // given a module. | |
| // collectFuncsFromModule uses FunctionSamples::getCanonicalFnName | |
| // which will query FunctionSamples::HasUniqSuffix, so it has to be | |
| // called after FunctionSamples::HasUniqSuffix is set, i.e. after | |
| // NameTable section is read. | |
| bool LoadFuncsToBeUsed = collectFuncsFromModule(); | |
| // When LoadFuncsToBeUsed is false, we are using LLVM tool, need to read all | |
| // profiles. | |
| if (!LoadFuncsToBeUsed) { | |
| while (Data < End) { | |
| if (std::error_code EC = readFuncProfile(Data)) | |
| return EC; | |
| } | |
| assert(Data == End && "More data is read than expected"); | |
| } else { | |
| // Load function profiles on demand. | |
| if (std::error_code EC = readFuncProfiles(FuncsToUse, Profiles)) | |
| return EC; | |
| Data = End; | |
| } | |
| assert((CSProfileCount == 0 || CSProfileCount == Profiles.size()) && | |
| "Cannot have both context-sensitive and regular profile"); | |
| assert((!CSProfileCount || ProfileIsCS) && | |
| "Section flag should be consistent with actual profile"); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::readProfileSymbolList(bool IsMD5) { | |
| if (IsMD5) | |
| return readMD5ProfileSymbolList(); | |
| return readStringBasedProfileSymbolList(); | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readMD5ProfileSymbolList() { | |
| size_t Size = End - Data; | |
| if (Size % sizeof(uint64_t) != 0) | |
| return sampleprof_error::truncated; | |
| const auto *Table = reinterpret_cast<const support::ulittle64_t *>(Data); | |
| size_t NumEntries = Size / sizeof(uint64_t); | |
| if (!ProfSymList) | |
| ProfSymList = std::make_unique<ProfileSymbolList>(); | |
| ProfSymList->setColdGUIDTable( | |
| EytzingerTableSpan<support::ulittle64_t>(Table, NumEntries)); | |
| Data = End; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::readStringBasedProfileSymbolList() { | |
| if (!ProfSymList) | |
| ProfSymList = std::make_unique<ProfileSymbolList>(); | |
| if (std::error_code EC = ProfSymList->read(Data, End - Data)) | |
| return EC; | |
| Data = End; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::decompressSection( | |
| const uint8_t *SecStart, const uint64_t SecSize, | |
| const uint8_t *&DecompressBuf, uint64_t &DecompressBufSize) { | |
| Data = SecStart; | |
| End = SecStart + SecSize; | |
| auto DecompressSize = readNumber<uint64_t>(); | |
| if (std::error_code EC = DecompressSize.getError()) | |
| return EC; | |
| DecompressBufSize = *DecompressSize; | |
| auto CompressSize = readNumber<uint64_t>(); | |
| if (std::error_code EC = CompressSize.getError()) | |
| return EC; | |
| if (!llvm::compression::zlib::isAvailable()) | |
| return sampleprof_error::zlib_unavailable; | |
| uint8_t *Buffer = Allocator.Allocate<uint8_t>(DecompressBufSize); | |
| size_t UCSize = DecompressBufSize; | |
| llvm::Error E = compression::zlib::decompress(ArrayRef(Data, *CompressSize), | |
| Buffer, UCSize); | |
| if (E) | |
| return sampleprof_error::uncompress_failed; | |
| DecompressBuf = reinterpret_cast<const uint8_t *>(Buffer); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readImpl() { | |
| const uint8_t *BufStart = | |
| reinterpret_cast<const uint8_t *>(Buffer->getBufferStart()); | |
| for (auto &Entry : SecHdrTable) { | |
| // Skip empty section. | |
| if (!Entry.Size) | |
| continue; | |
| // Skip sections without inlined functions when SkipFlatProf is true. | |
| if (SkipFlatProf && hasSecFlag(Entry, SecCommonFlags::SecFlagFlat)) | |
| continue; | |
| const uint8_t *SecStart = BufStart + Entry.Offset; | |
| uint64_t SecSize = Entry.Size; | |
| // If the section is compressed, decompress it into a buffer | |
| // DecompressBuf before reading the actual data. The pointee of | |
| // 'Data' will be changed to buffer hold by DecompressBuf | |
| // temporarily when reading the actual data. | |
| bool isCompressed = hasSecFlag(Entry, SecCommonFlags::SecFlagCompress); | |
| if (isCompressed) { | |
| const uint8_t *DecompressBuf; | |
| uint64_t DecompressBufSize; | |
| if (std::error_code EC = decompressSection( | |
| SecStart, SecSize, DecompressBuf, DecompressBufSize)) | |
| return EC; | |
| SecStart = DecompressBuf; | |
| SecSize = DecompressBufSize; | |
| } | |
| if (std::error_code EC = readOneSection(SecStart, SecSize, Entry)) | |
| return EC; | |
| if (Data != SecStart + SecSize) | |
| return sampleprof_error::malformed; | |
| // Change the pointee of 'Data' from DecompressBuf to original Buffer. | |
| if (isCompressed) { | |
| Data = BufStart + Entry.Offset; | |
| End = BufStart + Buffer->getBufferSize(); | |
| } | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderRawBinary::verifySPMagic(uint64_t Magic) { | |
| if (Magic == SPMagic()) | |
| return sampleprof_error::success; | |
| return sampleprof_error::bad_magic; | |
| } | |
| std::error_code SampleProfileReaderExtBinary::verifySPMagic(uint64_t Magic) { | |
| if (Magic == SPMagic(SPF_Ext_Binary)) | |
| return sampleprof_error::success; | |
| return sampleprof_error::bad_magic; | |
| } | |
| std::error_code SampleProfileReaderBinary::readNameTable() { | |
| auto Size = readNumber<size_t>(); | |
| if (std::error_code EC = Size.getError()) | |
| return EC; | |
| // Normally if useMD5 is true, the name table should have MD5 values, not | |
| // strings, however in the case that ExtBinary profile has multiple name | |
| // tables mixing string and MD5, all of them have to be normalized to use MD5, | |
| // because optimization passes can only handle either type. | |
| bool UseMD5 = useMD5(); | |
| std::vector<FunctionId> TableVec; | |
| TableVec.reserve(*Size); | |
| if (!ProfileIsCS) { | |
| MD5SampleContextTable.clear(); | |
| if (UseMD5) | |
| MD5SampleContextTable.reserve(*Size); | |
| else | |
| // If we are using strings, delay MD5 computation since only a portion of | |
| // names are used by top level functions. Use 0 to indicate MD5 value is | |
| // to be calculated as no known string has a MD5 value of 0. | |
| MD5SampleContextTable.resize(*Size); | |
| } | |
| for (size_t I = 0; I < *Size; ++I) { | |
| auto Name(readString()); | |
| if (std::error_code EC = Name.getError()) | |
| return EC; | |
| if (UseMD5) { | |
| FunctionId FID(*Name); | |
| if (!ProfileIsCS) | |
| MD5SampleContextTable.emplace_back(FID.getHashCode()); | |
| TableVec.emplace_back(FID); | |
| } else | |
| TableVec.push_back(FunctionId(*Name)); | |
| } | |
| if (!ProfileIsCS) | |
| MD5SampleContextStart = MD5SampleContextTable.data(); | |
| if (UseMD5) | |
| NameTable = | |
| std::make_unique<MD5SampleProfileNameTable>(std::move(TableVec)); | |
| else | |
| NameTable = | |
| std::make_unique<StringSampleProfileNameTable>(std::move(TableVec)); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readNameTableSec( | |
| bool IsMD5, bool FixedLengthMD5, bool IsEytzinger) { | |
| if (IsEytzinger) | |
| return readNameTableSecEytzinger(IsMD5, FixedLengthMD5); | |
| return readNameTableSecLegacy(IsMD5, FixedLengthMD5); | |
| } | |
| // Read the Eytzinger layout for SecNameTable from an ExtBinary MD5 profile. | |
| // | |
| // The section consists of three sequential ULEB128 symbol counts (Nested, Flat, | |
| // and Inlinees) followed by their corresponding arrays of 64-bit MD5 hash keys | |
| // laid out in Eytzinger order. | |
| std::error_code SampleProfileReaderExtBinaryBase::readNameTableSecEytzinger( | |
| bool IsMD5, bool FixedLengthMD5) { | |
| assert(IsMD5 && "Eytzinger name tables require MD5 representation"); | |
| if (!IsMD5) | |
| return sampleprof_error::malformed; | |
| // Read the table sizes for Nested, flat, and inlinee symbols. | |
| std::array<uint64_t, static_cast<size_t>(EytzingerSpan::NumSpans)> Counts; | |
| for (uint64_t &Count : Counts) { | |
| auto ValOrErr = readNumber<uint64_t>(); | |
| if (std::error_code EC = ValOrErr.getError()) | |
| return EC; | |
| Count = *ValOrErr; | |
| } | |
| auto [NumNested, NumFlat, NumInlinees] = Counts; | |
| // Guard against unsigned overflow in total entry computation. | |
| if (NumNested > std::numeric_limits<uint32_t>::max() || | |
| NumFlat > std::numeric_limits<uint32_t>::max() || | |
| NumInlinees > std::numeric_limits<uint32_t>::max()) | |
| return sampleprof_error::malformed; | |
| uint64_t TotalEntries = NumNested + NumFlat + NumInlinees; | |
| if (static_cast<size_t>(End - Data) < TotalEntries * sizeof(uint64_t)) | |
| return sampleprof_error::truncated; | |
| NameTable = std::make_unique<EytzingerSampleProfileNameTable>( | |
| reinterpret_cast<const support::ulittle64_t *>(Data), NumNested, NumFlat, | |
| NumInlinees); | |
| if (!ProfileIsCS) | |
| MD5SampleContextStart = reinterpret_cast<const uint64_t *>(Data); | |
| Data = Data + TotalEntries * sizeof(uint64_t); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::readNameTableSecLegacy(bool IsMD5, | |
| bool FixedLengthMD5) { | |
| if (FixedLengthMD5) { | |
| if (!IsMD5) | |
| errs() << "If FixedLengthMD5 is true, UseMD5 has to be true"; | |
| auto Size = readNumber<size_t>(); | |
| if (std::error_code EC = Size.getError()) | |
| return EC; | |
| assert(Data + (*Size) * sizeof(uint64_t) == End && | |
| "Fixed length MD5 name table does not contain specified number of " | |
| "entries"); | |
| if (Data + (*Size) * sizeof(uint64_t) > End) | |
| return sampleprof_error::truncated; | |
| if (LazyLoadNameTable) { | |
| NameTable = std::make_unique<LazySampleProfileNameTable>(Data, *Size); | |
| } else { | |
| std::vector<FunctionId> TableVec; | |
| TableVec.reserve(*Size); | |
| for (size_t I = 0; I < *Size; ++I) { | |
| using namespace support; | |
| uint64_t FID = endian::read<uint64_t, unaligned>( | |
| Data + I * sizeof(uint64_t), endianness::little); | |
| TableVec.emplace_back(FunctionId(FID)); | |
| } | |
| NameTable = | |
| std::make_unique<MD5SampleProfileNameTable>(std::move(TableVec)); | |
| } | |
| if (!ProfileIsCS) | |
| MD5SampleContextStart = reinterpret_cast<const uint64_t *>(Data); | |
| Data = Data + (*Size) * sizeof(uint64_t); | |
| return sampleprof_error::success; | |
| } | |
| if (IsMD5) { | |
| assert(!FixedLengthMD5 && "FixedLengthMD5 should be unreachable here"); | |
| auto Size = readNumber<size_t>(); | |
| if (std::error_code EC = Size.getError()) | |
| return EC; | |
| std::vector<FunctionId> TableVec; | |
| TableVec.reserve(*Size); | |
| if (!ProfileIsCS) | |
| MD5SampleContextTable.resize(*Size); | |
| for (size_t I = 0; I < *Size; ++I) { | |
| auto FID = readNumber<uint64_t>(); | |
| if (std::error_code EC = FID.getError()) | |
| return EC; | |
| if (!ProfileIsCS) | |
| support::endian::write64le(&MD5SampleContextTable[I], *FID); | |
| TableVec.emplace_back(FunctionId(*FID)); | |
| } | |
| if (!ProfileIsCS) | |
| MD5SampleContextStart = MD5SampleContextTable.data(); | |
| NameTable = | |
| std::make_unique<MD5SampleProfileNameTable>(std::move(TableVec)); | |
| return sampleprof_error::success; | |
| } | |
| return SampleProfileReaderBinary::readNameTable(); | |
| } | |
| // Read in the CS name table section, which basically contains a list of context | |
| // vectors. Each element of a context vector, aka a frame, refers to the | |
| // underlying raw function names that are stored in the name table, as well as | |
| // a callsite identifier that only makes sense for non-leaf frames. | |
| std::error_code SampleProfileReaderExtBinaryBase::readCSNameTableSec() { | |
| auto Size = readNumber<size_t>(); | |
| if (std::error_code EC = Size.getError()) | |
| return EC; | |
| CSNameTable.clear(); | |
| CSNameTable.reserve(*Size); | |
| if (ProfileIsCS) { | |
| // Delay MD5 computation of CS context until they are needed. Use 0 to | |
| // indicate MD5 value is to be calculated as no known string has a MD5 | |
| // value of 0. | |
| MD5SampleContextTable.clear(); | |
| MD5SampleContextTable.resize(*Size); | |
| MD5SampleContextStart = MD5SampleContextTable.data(); | |
| } | |
| for (size_t I = 0; I < *Size; ++I) { | |
| CSNameTable.emplace_back(SampleContextFrameVector()); | |
| auto ContextSize = readNumber<uint32_t>(); | |
| if (std::error_code EC = ContextSize.getError()) | |
| return EC; | |
| for (uint32_t J = 0; J < *ContextSize; ++J) { | |
| auto FName(readStringFromTable()); | |
| if (std::error_code EC = FName.getError()) | |
| return EC; | |
| auto LineOffset = readNumber<uint64_t>(); | |
| if (std::error_code EC = LineOffset.getError()) | |
| return EC; | |
| if (!isOffsetLegal(*LineOffset)) | |
| return sampleprof_error::illegal_line_offset; | |
| auto Discriminator = readNumber<uint64_t>(); | |
| if (std::error_code EC = Discriminator.getError()) | |
| return EC; | |
| CSNameTable.back().emplace_back( | |
| FName.get(), LineLocation(LineOffset.get(), Discriminator.get())); | |
| } | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::readFuncMetadata(FunctionSamples *FProfile) { | |
| if (Data < End) { | |
| if (ProfileIsProbeBased) { | |
| auto Checksum = readNumber<uint64_t>(); | |
| if (std::error_code EC = Checksum.getError()) | |
| return EC; | |
| if (FProfile) | |
| FProfile->setFunctionHash(*Checksum); | |
| } | |
| if (ProfileHasAttribute) { | |
| auto Attributes = readNumber<uint32_t>(); | |
| if (std::error_code EC = Attributes.getError()) | |
| return EC; | |
| if (FProfile) | |
| FProfile->getContext().setAllAttributes(*Attributes); | |
| } | |
| if (!ProfileIsCS) { | |
| // Read all the attributes for inlined function calls. | |
| auto NumCallsites = readNumber<uint32_t>(); | |
| if (std::error_code EC = NumCallsites.getError()) | |
| return EC; | |
| for (uint32_t J = 0; J < *NumCallsites; ++J) { | |
| auto LineOffset = readNumber<uint64_t>(); | |
| if (std::error_code EC = LineOffset.getError()) | |
| return EC; | |
| auto Discriminator = readNumber<uint64_t>(); | |
| if (std::error_code EC = Discriminator.getError()) | |
| return EC; | |
| auto FContextHash(readSampleContextFromTable()); | |
| if (std::error_code EC = FContextHash.getError()) | |
| return EC; | |
| auto &[FContext, Hash] = *FContextHash; | |
| FunctionSamples *CalleeProfile = nullptr; | |
| if (FProfile) { | |
| CalleeProfile = const_cast<FunctionSamples *>( | |
| &FProfile->functionSamplesAt(LineLocation( | |
| *LineOffset, *Discriminator))[FContext.getFunction()]); | |
| } | |
| if (std::error_code EC = readFuncMetadata(CalleeProfile)) | |
| return EC; | |
| } | |
| } | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readFuncMetadata( | |
| DenseSet<FunctionSamples *> &Profiles) { | |
| if (FuncMetadataIndex.empty()) | |
| return sampleprof_error::success; | |
| for (auto *FProfile : Profiles) { | |
| auto R = FuncMetadataIndex.find(FProfile->getContext().getHashCode()); | |
| if (R == FuncMetadataIndex.end()) | |
| continue; | |
| Data = R->second.first; | |
| End = R->second.second; | |
| if (std::error_code EC = readFuncMetadata(FProfile)) | |
| return EC; | |
| assert(Data == End && "More data is read than expected"); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readFuncMetadata() { | |
| while (Data < End) { | |
| auto FContextHash(readSampleContextFromTable()); | |
| if (std::error_code EC = FContextHash.getError()) | |
| return EC; | |
| auto &[FContext, Hash] = *FContextHash; | |
| FunctionSamples *FProfile = nullptr; | |
| auto It = Profiles.find(FContext); | |
| if (It != Profiles.end()) | |
| FProfile = &It->second; | |
| const uint8_t *Start = Data; | |
| if (std::error_code EC = readFuncMetadata(FProfile)) | |
| return EC; | |
| FuncMetadataIndex[FContext.getHashCode()] = {Start, Data}; | |
| } | |
| assert(Data == End && "More data is read than expected"); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileReaderExtBinaryBase::readSecHdrTableEntry(uint64_t Idx) { | |
| SecHdrTableEntry Entry; | |
| auto Type = readUnencodedNumber<uint64_t>(); | |
| if (std::error_code EC = Type.getError()) | |
| return EC; | |
| Entry.Type = static_cast<SecType>(*Type); | |
| // Reject a section whose encoding is newer than the declared file version. | |
| if ((Entry.Type == SecCompositeProfile || | |
| Entry.Type == SecCompositeFuncOffsetTable) && | |
| FormatVersion < CompositeProfileVersion) | |
| return sampleprof_error::unsupported_version; | |
| auto Flags = readUnencodedNumber<uint64_t>(); | |
| if (std::error_code EC = Flags.getError()) | |
| return EC; | |
| Entry.Flags = *Flags; | |
| auto Offset = readUnencodedNumber<uint64_t>(); | |
| if (std::error_code EC = Offset.getError()) | |
| return EC; | |
| Entry.Offset = *Offset; | |
| auto Size = readUnencodedNumber<uint64_t>(); | |
| if (std::error_code EC = Size.getError()) | |
| return EC; | |
| Entry.Size = *Size; | |
| Entry.LayoutIndex = Idx; | |
| SecHdrTable.push_back(std::move(Entry)); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readSecHdrTable() { | |
| auto EntryNum = readUnencodedNumber<uint64_t>(); | |
| if (std::error_code EC = EntryNum.getError()) | |
| return EC; | |
| for (uint64_t i = 0; i < (*EntryNum); i++) | |
| if (std::error_code EC = readSecHdrTableEntry(i)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderExtBinaryBase::readHeader() { | |
| const uint8_t *BufStart = | |
| reinterpret_cast<const uint8_t *>(Buffer->getBufferStart()); | |
| Data = BufStart; | |
| End = BufStart + Buffer->getBufferSize(); | |
| if (std::error_code EC = readMagicIdent()) | |
| return EC; | |
| if (std::error_code EC = readSecHdrTable()) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| uint64_t SampleProfileReaderExtBinaryBase::getSectionSize(SecType Type) { | |
| uint64_t Size = 0; | |
| for (auto &Entry : SecHdrTable) { | |
| if (Entry.Type == Type) | |
| Size += Entry.Size; | |
| } | |
| return Size; | |
| } | |
| uint64_t SampleProfileReaderExtBinaryBase::getFileSize() { | |
| // Sections in SecHdrTable is not necessarily in the same order as | |
| // sections in the profile because section like FuncOffsetTable needs | |
| // to be written after section LBRProfile but needs to be read before | |
| // section LBRProfile, so we cannot simply use the last entry in | |
| // SecHdrTable to calculate the file size. | |
| uint64_t FileSize = 0; | |
| for (auto &Entry : SecHdrTable) { | |
| FileSize = std::max(Entry.Offset + Entry.Size, FileSize); | |
| } | |
| return FileSize; | |
| } | |
| static std::string getSecFlagsStr(const SecHdrTableEntry &Entry) { | |
| std::string Flags; | |
| if (hasSecFlag(Entry, SecCommonFlags::SecFlagCompress)) | |
| Flags.append("{compressed,"); | |
| else | |
| Flags.append("{"); | |
| if (hasSecFlag(Entry, SecCommonFlags::SecFlagFlat)) | |
| Flags.append("flat,"); | |
| switch (Entry.Type) { | |
| case SecNameTable: | |
| if (hasSecFlag(Entry, SecNameTableFlags::SecFlagEytzinger)) | |
| Flags.append("eytzinger,"); | |
| if (hasSecFlag(Entry, SecNameTableFlags::SecFlagFixedLengthMD5)) | |
| Flags.append("fixlenmd5,"); | |
| else if (hasSecFlag(Entry, SecNameTableFlags::SecFlagMD5Name)) | |
| Flags.append("md5,"); | |
| if (hasSecFlag(Entry, SecNameTableFlags::SecFlagUniqSuffix)) | |
| Flags.append("uniq,"); | |
| break; | |
| case SecProfSummary: | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagPartial)) | |
| Flags.append("partial,"); | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFullContext)) | |
| Flags.append("context,"); | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagIsPreInlined)) | |
| Flags.append("preInlined,"); | |
| if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFSDiscriminator)) | |
| Flags.append("fs-discriminator,"); | |
| break; | |
| case SecFuncOffsetTable: | |
| case SecCompositeFuncOffsetTable: | |
| if (hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagOrdered)) | |
| Flags.append("ordered,"); | |
| if (hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagEytzinger)) | |
| Flags.append("eytzinger,"); | |
| break; | |
| case SecFuncMetadata: | |
| if (hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagIsProbeBased)) | |
| Flags.append("probe,"); | |
| if (hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagHasAttribute)) | |
| Flags.append("attr,"); | |
| break; | |
| case SecProfileSymbolList: | |
| if (hasSecFlag(Entry, SecProfileSymbolListFlags::SecFlagMD5)) | |
| Flags.append("md5,"); | |
| break; | |
| default: | |
| break; | |
| } | |
| char &last = Flags.back(); | |
| if (last == ',') | |
| last = '}'; | |
| else | |
| Flags.append("}"); | |
| return Flags; | |
| } | |
| bool SampleProfileReaderExtBinaryBase::dumpSectionInfo(raw_ostream &OS) { | |
| uint64_t TotalSecsSize = 0; | |
| for (auto &Entry : SecHdrTable) { | |
| OS << getSecName(Entry.Type) << " - Offset: " << Entry.Offset | |
| << ", Size: " << Entry.Size << ", Flags: " << getSecFlagsStr(Entry) | |
| << "\n"; | |
| ; | |
| TotalSecsSize += Entry.Size; | |
| } | |
| uint64_t HeaderSize = SecHdrTable.front().Offset; | |
| assert(HeaderSize + TotalSecsSize == getFileSize() && | |
| "Size of 'header + sections' doesn't match the total size of profile"); | |
| OS << "Header Size: " << HeaderSize << "\n"; | |
| OS << "Total Sections Size: " << TotalSecsSize << "\n"; | |
| OS << "File Size: " << getFileSize() << "\n"; | |
| return true; | |
| } | |
| std::error_code SampleProfileReaderBinary::readMagicIdent() { | |
| // Read and check the magic identifier. | |
| auto Magic = readNumber<uint64_t>(); | |
| if (std::error_code EC = Magic.getError()) | |
| return EC; | |
| else if (std::error_code EC = verifySPMagic(*Magic)) | |
| return EC; | |
| // Read the version number. | |
| auto Version = readNumber<uint64_t>(); | |
| if (std::error_code EC = Version.getError()) | |
| return EC; | |
| else if (!formatVersionIsSupported(*Version)) | |
| return sampleprof_error::unsupported_version; | |
| FormatVersion = *Version; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderBinary::readHeader() { | |
| Data = reinterpret_cast<const uint8_t *>(Buffer->getBufferStart()); | |
| End = Data + Buffer->getBufferSize(); | |
| if (std::error_code EC = readMagicIdent()) | |
| return EC; | |
| if (std::error_code EC = readSummary()) | |
| return EC; | |
| if (std::error_code EC = readNameTable()) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderBinary::readSummaryEntry( | |
| std::vector<ProfileSummaryEntry> &Entries) { | |
| auto Cutoff = readNumber<uint64_t>(); | |
| if (std::error_code EC = Cutoff.getError()) | |
| return EC; | |
| auto MinBlockCount = readNumber<uint64_t>(); | |
| if (std::error_code EC = MinBlockCount.getError()) | |
| return EC; | |
| auto NumBlocks = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumBlocks.getError()) | |
| return EC; | |
| Entries.emplace_back(*Cutoff, *MinBlockCount, *NumBlocks); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderBinary::readSummary() { | |
| auto TotalCount = readNumber<uint64_t>(); | |
| if (std::error_code EC = TotalCount.getError()) | |
| return EC; | |
| auto MaxBlockCount = readNumber<uint64_t>(); | |
| if (std::error_code EC = MaxBlockCount.getError()) | |
| return EC; | |
| auto MaxFunctionCount = readNumber<uint64_t>(); | |
| if (std::error_code EC = MaxFunctionCount.getError()) | |
| return EC; | |
| auto NumBlocks = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumBlocks.getError()) | |
| return EC; | |
| auto NumFunctions = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumFunctions.getError()) | |
| return EC; | |
| auto NumSummaryEntries = readNumber<uint64_t>(); | |
| if (std::error_code EC = NumSummaryEntries.getError()) | |
| return EC; | |
| std::vector<ProfileSummaryEntry> Entries; | |
| for (unsigned i = 0; i < *NumSummaryEntries; i++) { | |
| std::error_code EC = readSummaryEntry(Entries); | |
| if (EC != sampleprof_error::success) | |
| return EC; | |
| } | |
| Summary = std::make_unique<ProfileSummary>( | |
| ProfileSummary::PSK_Sample, Entries, *TotalCount, *MaxBlockCount, 0, | |
| *MaxFunctionCount, *NumBlocks, *NumFunctions); | |
| return sampleprof_error::success; | |
| } | |
| /// Return whether Buffer starts with ExpectedMagic without reading beyond it. | |
| static bool hasBinaryFormat(const MemoryBuffer &Buffer, | |
| uint64_t ExpectedMagic) { | |
| const uint8_t *Data = | |
| reinterpret_cast<const uint8_t *>(Buffer.getBufferStart()); | |
| const uint8_t *End = reinterpret_cast<const uint8_t *>(Buffer.getBufferEnd()); | |
| ULEB128DecodeError DecodeError = ULEB128DecodeError::None; | |
| uint64_t Magic = decodeULEB128(Data, nullptr, End, nullptr, &DecodeError); | |
| return DecodeError == ULEB128DecodeError::None && Magic == ExpectedMagic; | |
| } | |
| bool SampleProfileReaderRawBinary::hasFormat(const MemoryBuffer &Buffer) { | |
| return hasBinaryFormat(Buffer, SPMagic()); | |
| } | |
| bool SampleProfileReaderExtBinary::hasFormat(const MemoryBuffer &Buffer) { | |
| return hasBinaryFormat(Buffer, SPMagic(SPF_Ext_Binary)); | |
| } | |
| std::error_code SampleProfileReaderGCC::skipNextWord() { | |
| uint32_t dummy; | |
| if (!GcovBuffer.readInt(dummy)) | |
| return sampleprof_error::truncated; | |
| return sampleprof_error::success; | |
| } | |
| template <typename T> ErrorOr<T> SampleProfileReaderGCC::readNumber() { | |
| if (sizeof(T) <= sizeof(uint32_t)) { | |
| uint32_t Val; | |
| if (GcovBuffer.readInt(Val) && Val <= std::numeric_limits<T>::max()) | |
| return static_cast<T>(Val); | |
| } else if (sizeof(T) <= sizeof(uint64_t)) { | |
| uint64_t Val; | |
| if (GcovBuffer.readInt64(Val) && Val <= std::numeric_limits<T>::max()) | |
| return static_cast<T>(Val); | |
| } | |
| std::error_code EC = sampleprof_error::malformed; | |
| reportError(0, EC.message()); | |
| return EC; | |
| } | |
| ErrorOr<StringRef> SampleProfileReaderGCC::readString() { | |
| StringRef Str; | |
| if (!GcovBuffer.readString(Str)) | |
| return sampleprof_error::truncated; | |
| return Str; | |
| } | |
| std::error_code SampleProfileReaderGCC::readHeader() { | |
| // Read the magic identifier. | |
| if (!GcovBuffer.readGCDAFormat()) | |
| return sampleprof_error::unrecognized_format; | |
| // Read the version number. Note - the GCC reader does not validate this | |
| // version, but the profile creator generates v704. | |
| GCOV::GCOVVersion version; | |
| if (!GcovBuffer.readGCOVVersion(version)) | |
| return sampleprof_error::unrecognized_format; | |
| if (version != GCOV::V407) | |
| return sampleprof_error::unsupported_version; | |
| // Skip the empty integer. | |
| if (std::error_code EC = skipNextWord()) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderGCC::readSectionTag(uint32_t Expected) { | |
| uint32_t Tag; | |
| if (!GcovBuffer.readInt(Tag)) | |
| return sampleprof_error::truncated; | |
| if (Tag != Expected) | |
| return sampleprof_error::malformed; | |
| if (std::error_code EC = skipNextWord()) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderGCC::readNameTable() { | |
| if (std::error_code EC = readSectionTag(GCOVTagAFDOFileNames)) | |
| return EC; | |
| uint32_t Size; | |
| if (!GcovBuffer.readInt(Size)) | |
| return sampleprof_error::truncated; | |
| for (uint32_t I = 0; I < Size; ++I) { | |
| StringRef Str; | |
| if (!GcovBuffer.readString(Str)) | |
| return sampleprof_error::truncated; | |
| Names.push_back(std::string(Str)); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderGCC::readFunctionProfiles() { | |
| if (std::error_code EC = readSectionTag(GCOVTagAFDOFunction)) | |
| return EC; | |
| uint32_t NumFunctions; | |
| if (!GcovBuffer.readInt(NumFunctions)) | |
| return sampleprof_error::truncated; | |
| InlineCallStack Stack; | |
| for (uint32_t I = 0; I < NumFunctions; ++I) | |
| if (std::error_code EC = readOneFunctionProfile(Stack, true, 0)) | |
| return EC; | |
| computeSummary(); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileReaderGCC::readOneFunctionProfile( | |
| const InlineCallStack &InlineStack, bool Update, uint32_t Offset) { | |
| uint64_t HeadCount = 0; | |
| if (InlineStack.size() == 0) | |
| if (!GcovBuffer.readInt64(HeadCount)) | |
| return sampleprof_error::truncated; | |
| uint32_t NameIdx; | |
| if (!GcovBuffer.readInt(NameIdx)) | |
| return sampleprof_error::truncated; | |
| StringRef Name(Names[NameIdx]); | |
| uint32_t NumPosCounts; | |
| if (!GcovBuffer.readInt(NumPosCounts)) | |
| return sampleprof_error::truncated; | |
| uint32_t NumCallsites; | |
| if (!GcovBuffer.readInt(NumCallsites)) | |
| return sampleprof_error::truncated; | |
| FunctionSamples *FProfile = nullptr; | |
| if (InlineStack.size() == 0) { | |
| // If this is a top function that we have already processed, do not | |
| // update its profile again. This happens in the presence of | |
| // function aliases. Since these aliases share the same function | |
| // body, there will be identical replicated profiles for the | |
| // original function. In this case, we simply not bother updating | |
| // the profile of the original function. | |
| FProfile = &Profiles[FunctionId(Name)]; | |
| FProfile->addHeadSamples(HeadCount); | |
| if (FProfile->getTotalSamples() > 0) | |
| Update = false; | |
| } else { | |
| // Otherwise, we are reading an inlined instance. The top of the | |
| // inline stack contains the profile of the caller. Insert this | |
| // callee in the caller's CallsiteMap. | |
| FunctionSamples *CallerProfile = InlineStack.front(); | |
| uint32_t LineOffset = Offset >> 16; | |
| uint32_t Discriminator = Offset & 0xffff; | |
| FProfile = &CallerProfile->functionSamplesAt( | |
| LineLocation(LineOffset, Discriminator))[FunctionId(Name)]; | |
| } | |
| FProfile->setFunction(FunctionId(Name)); | |
| FProfile->reserveBodySamples(NumPosCounts); | |
| for (uint32_t I = 0; I < NumPosCounts; ++I) { | |
| uint32_t Offset; | |
| if (!GcovBuffer.readInt(Offset)) | |
| return sampleprof_error::truncated; | |
| uint32_t NumTargets; | |
| if (!GcovBuffer.readInt(NumTargets)) | |
| return sampleprof_error::truncated; | |
| uint64_t Count; | |
| if (!GcovBuffer.readInt64(Count)) | |
| return sampleprof_error::truncated; | |
| // The line location is encoded in the offset as: | |
| // high 16 bits: line offset to the start of the function. | |
| // low 16 bits: discriminator. | |
| uint32_t LineOffset = Offset >> 16; | |
| uint32_t Discriminator = Offset & 0xffff; | |
| InlineCallStack NewStack; | |
| NewStack.push_back(FProfile); | |
| llvm::append_range(NewStack, InlineStack); | |
| if (Update) { | |
| // Walk up the inline stack, adding the samples on this line to | |
| // the total sample count of the callers in the chain. | |
| for (auto *CallerProfile : NewStack) | |
| CallerProfile->addTotalSamples(Count); | |
| // Update the body samples for the current profile. | |
| FProfile->addBodySamples(LineOffset, Discriminator, Count); | |
| } | |
| // Process the list of functions called at an indirect call site. | |
| // These are all the targets that a function pointer (or virtual | |
| // function) resolved at runtime. | |
| for (uint32_t J = 0; J < NumTargets; J++) { | |
| uint32_t HistVal; | |
| if (!GcovBuffer.readInt(HistVal)) | |
| return sampleprof_error::truncated; | |
| if (HistVal != HIST_TYPE_INDIR_CALL_TOPN) | |
| return sampleprof_error::malformed; | |
| uint64_t TargetIdx; | |
| if (!GcovBuffer.readInt64(TargetIdx)) | |
| return sampleprof_error::truncated; | |
| StringRef TargetName(Names[TargetIdx]); | |
| uint64_t TargetCount; | |
| if (!GcovBuffer.readInt64(TargetCount)) | |
| return sampleprof_error::truncated; | |
| if (Update) | |
| FProfile->addCalledTargetSamples(LineOffset, Discriminator, | |
| FunctionId(TargetName), TargetCount); | |
| } | |
| } | |
| // Process all the inlined callers into the current function. These | |
| // are all the callsites that were inlined into this function. | |
| for (uint32_t I = 0; I < NumCallsites; I++) { | |
| // The offset is encoded as: | |
| // high 16 bits: line offset to the start of the function. | |
| // low 16 bits: discriminator. | |
| uint32_t Offset; | |
| if (!GcovBuffer.readInt(Offset)) | |
| return sampleprof_error::truncated; | |
| InlineCallStack NewStack; | |
| NewStack.push_back(FProfile); | |
| llvm::append_range(NewStack, InlineStack); | |
| if (std::error_code EC = readOneFunctionProfile(NewStack, Update, Offset)) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| /// Read a GCC AutoFDO profile. | |
| /// | |
| /// This format is generated by the Linux Perf conversion tool at | |
| /// https://github.com/google/autofdo. | |
| std::error_code SampleProfileReaderGCC::readImpl() { | |
| assert(!ProfileIsFSDisciminator && "Gcc profiles not support FSDisciminator"); | |
| // Read the string table. | |
| if (std::error_code EC = readNameTable()) | |
| return EC; | |
| // Read the source profile. | |
| if (std::error_code EC = readFunctionProfiles()) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| bool SampleProfileReaderGCC::hasFormat(const MemoryBuffer &Buffer) { | |
| StringRef Contents = Buffer.getBuffer(); | |
| // Preserve exact magic matching, including a magic-only eight-byte buffer. | |
| return Contents.starts_with("adcg*704") && | |
| (Contents.size() == 8 || Contents[8] == '\0'); | |
| } | |
| void SampleProfileReaderItaniumRemapper::applyRemapping(LLVMContext &Ctx) { | |
| // If the reader uses MD5 to represent string, we can't remap it because | |
| // we don't know what the original function names were. | |
| if (Reader.useMD5()) { | |
| Ctx.diagnose(DiagnosticInfoSampleProfile( | |
| Reader.getBuffer()->getBufferIdentifier(), | |
| "Profile data remapping cannot be applied to profile data " | |
| "using MD5 names (original mangled names are not available).", | |
| DS_Warning)); | |
| return; | |
| } | |
| // CSSPGO-TODO: Remapper is not yet supported. | |
| // We will need to remap the entire context string. | |
| assert(Remappings && "should be initialized while creating remapper"); | |
| for (auto &Sample : Reader.getProfiles()) { | |
| DenseSet<FunctionId> NamesInSample; | |
| Sample.second.findAllNames(NamesInSample); | |
| for (auto &Name : NamesInSample) { | |
| StringRef NameStr = Name.stringRef(); | |
| if (auto Key = Remappings->insert(NameStr)) | |
| NameMap.insert({Key, NameStr}); | |
| } | |
| } | |
| RemappingApplied = true; | |
| } | |
| std::optional<StringRef> | |
| SampleProfileReaderItaniumRemapper::lookUpNameInProfile(StringRef Fname) { | |
| if (auto Key = Remappings->lookup(Fname)) { | |
| StringRef Result = NameMap.lookup(Key); | |
| if (!Result.empty()) | |
| return Result; | |
| } | |
| return std::nullopt; | |
| } | |
| /// Prepare a memory buffer for the contents of \p Filename. | |
| /// | |
| /// \returns an error code indicating the status of the buffer. | |
| static ErrorOr<std::unique_ptr<MemoryBuffer>> | |
| setupMemoryBuffer(const Twine &Filename, vfs::FileSystem &FS) { | |
| auto BufferOrErr = Filename.str() == "-" ? MemoryBuffer::getSTDIN() | |
| : FS.getBufferForFile(Filename); | |
| if (std::error_code EC = BufferOrErr.getError()) | |
| return EC; | |
| auto Buffer = std::move(BufferOrErr.get()); | |
| return std::move(Buffer); | |
| } | |
| /// Create a sample profile reader based on the format of the input file. | |
| /// | |
| /// \param Filename The file to open. | |
| /// | |
| /// \param C The LLVM context to use to emit diagnostics. | |
| /// | |
| /// \param P The FSDiscriminatorPass. | |
| /// | |
| /// \param RemapFilename The file used for profile remapping. | |
| /// | |
| /// \returns an error code indicating the status of the created reader. | |
| ErrorOr<std::unique_ptr<SampleProfileReader>> | |
| SampleProfileReader::create(StringRef Filename, LLVMContext &C, | |
| vfs::FileSystem &FS, FSDiscriminatorPass P, | |
| StringRef RemapFilename) { | |
| auto BufferOrError = setupMemoryBuffer(Filename, FS); | |
| if (std::error_code EC = BufferOrError.getError()) | |
| return EC; | |
| return create(BufferOrError.get(), C, FS, P, RemapFilename); | |
| } | |
| /// Create a sample profile remapper from the given input, to remap the | |
| /// function names in the given profile data. | |
| /// | |
| /// \param Filename The file to open. | |
| /// | |
| /// \param Reader The profile reader the remapper is going to be applied to. | |
| /// | |
| /// \param C The LLVM context to use to emit diagnostics. | |
| /// | |
| /// \returns an error code indicating the status of the created reader. | |
| ErrorOr<std::unique_ptr<SampleProfileReaderItaniumRemapper>> | |
| SampleProfileReaderItaniumRemapper::create(StringRef Filename, | |
| vfs::FileSystem &FS, | |
| SampleProfileReader &Reader, | |
| LLVMContext &C) { | |
| auto BufferOrError = setupMemoryBuffer(Filename, FS); | |
| if (std::error_code EC = BufferOrError.getError()) | |
| return EC; | |
| return create(BufferOrError.get(), Reader, C); | |
| } | |
| /// Create a sample profile remapper from the given input, to remap the | |
| /// function names in the given profile data. | |
| /// | |
| /// \param B The memory buffer to create the reader from (assumes ownership). | |
| /// | |
| /// \param C The LLVM context to use to emit diagnostics. | |
| /// | |
| /// \param Reader The profile reader the remapper is going to be applied to. | |
| /// | |
| /// \returns an error code indicating the status of the created reader. | |
| ErrorOr<std::unique_ptr<SampleProfileReaderItaniumRemapper>> | |
| SampleProfileReaderItaniumRemapper::create(std::unique_ptr<MemoryBuffer> &B, | |
| SampleProfileReader &Reader, | |
| LLVMContext &C) { | |
| auto Remappings = std::make_unique<SymbolRemappingReader>(); | |
| if (Error E = Remappings->read(*B)) { | |
| handleAllErrors( | |
| std::move(E), [&](const SymbolRemappingParseError &ParseError) { | |
| C.diagnose(DiagnosticInfoSampleProfile(B->getBufferIdentifier(), | |
| ParseError.getLineNum(), | |
| ParseError.getMessage())); | |
| }); | |
| return sampleprof_error::malformed; | |
| } | |
| return std::make_unique<SampleProfileReaderItaniumRemapper>( | |
| std::move(B), std::move(Remappings), Reader); | |
| } | |
| /// Create a sample profile reader based on the format of the input data. | |
| /// | |
| /// \param B The memory buffer to create the reader from (assumes ownership). | |
| /// | |
| /// \param C The LLVM context to use to emit diagnostics. | |
| /// | |
| /// \param P The FSDiscriminatorPass. | |
| /// | |
| /// \param RemapFilename The file used for profile remapping. | |
| /// | |
| /// \returns an error code indicating the status of the created reader. | |
| ErrorOr<std::unique_ptr<SampleProfileReader>> | |
| SampleProfileReader::create(std::unique_ptr<MemoryBuffer> &B, LLVMContext &C, | |
| vfs::FileSystem &FS, FSDiscriminatorPass P, | |
| StringRef RemapFilename) { | |
| std::unique_ptr<SampleProfileReader> Reader; | |
| if (SampleProfileReaderRawBinary::hasFormat(*B)) | |
| Reader.reset(new SampleProfileReaderRawBinary(std::move(B), C)); | |
| else if (SampleProfileReaderExtBinary::hasFormat(*B)) | |
| Reader.reset(new SampleProfileReaderExtBinary(std::move(B), C)); | |
| else if (SampleProfileReaderGCC::hasFormat(*B)) | |
| Reader.reset(new SampleProfileReaderGCC(std::move(B), C)); | |
| else if (SampleProfileReaderText::hasFormat(*B)) | |
| Reader.reset(new SampleProfileReaderText(std::move(B), C)); | |
| else | |
| return sampleprof_error::unrecognized_format; | |
| if (!RemapFilename.empty()) { | |
| auto ReaderOrErr = SampleProfileReaderItaniumRemapper::create( | |
| RemapFilename, FS, *Reader, C); | |
| if (std::error_code EC = ReaderOrErr.getError()) { | |
| std::string Msg = "Could not create remapper: " + EC.message(); | |
| C.diagnose(DiagnosticInfoSampleProfile(RemapFilename, Msg)); | |
| return EC; | |
| } | |
| Reader->Remapper = std::move(ReaderOrErr.get()); | |
| } | |
| if (std::error_code EC = Reader->readHeader()) { | |
| return EC; | |
| } | |
| Reader->setDiscriminatorMaskedBitFrom(P); | |
| return std::move(Reader); | |
| } | |
| // For text and GCC file formats, we compute the summary after reading the | |
| // profile. Binary format has the profile summary in its header. | |
| void SampleProfileReader::computeSummary() { | |
| SampleProfileSummaryBuilder Builder(ProfileSummaryBuilder::DefaultCutoffs); | |
| Summary = Builder.computeSummaryForProfiles(Profiles); | |
| } |