//===----------------------------------------------------------------------===//
//
// This file implements the class that reads LLVM sample profiles. It
-// supports two file formats: text and bitcode. The textual representation
-// is useful for debugging and testing purposes. The bitcode representation
+// supports two file formats: text and binary. The textual representation
+// is useful for debugging and testing purposes. The binary representation
// is more compact, resulting in smaller file sizes. However, they can
// both be used interchangeably.
//
#include "llvm/ProfileData/SampleProfReader.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorOr.h"
-#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/LEB128.h"
#include "llvm/Support/LineIterator.h"
+#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Regex.h"
-using namespace sampleprof;
+using namespace llvm::sampleprof;
using namespace llvm;
/// \brief Print the samples collected for a function on stream \p OS.
void FunctionSamples::print(raw_ostream &OS) {
OS << TotalSamples << ", " << TotalHeadSamples << ", " << BodySamples.size()
<< " sampled lines\n";
- for (BodySampleMap::const_iterator SI = BodySamples.begin(),
- SE = BodySamples.end();
- SI != SE; ++SI)
- OS << "\tline offset: " << SI->first.LineOffset
- << ", discriminator: " << SI->first.Discriminator
- << ", number of samples: " << SI->second << "\n";
+ for (const auto &SI : BodySamples) {
+ LineLocation Loc = SI.first;
+ const SampleRecord &Sample = SI.second;
+ OS << "\tline offset: " << Loc.LineOffset
+ << ", discriminator: " << Loc.Discriminator
+ << ", number of samples: " << Sample.getSamples();
+ if (Sample.hasCalls()) {
+ OS << ", calls:";
+ for (const auto &I : Sample.getCallTargets())
+ OS << " " << I.first() << ":" << I.second;
+ }
+ OS << "\n";
+ }
OS << "\n";
}
-/// \brief Print the function profile for \p FName on stream \p OS.
-///
-/// \param OS Stream to emit the output to.
-/// \param FName Name of the function to print.
-void SampleProfileReader::printFunctionProfile(raw_ostream &OS,
- StringRef FName) {
- OS << "Function: " << FName << ":\n";
- Profiles[FName].print(OS);
-}
-
/// \brief Dump the function profile for \p FName.
///
/// \param FName Name of the function to print.
-void SampleProfileReader::dumpFunctionProfile(StringRef FName) {
- printFunctionProfile(dbgs(), FName);
+/// \param OS Stream to emit the output to.
+void SampleProfileReader::dumpFunctionProfile(StringRef FName,
+ raw_ostream &OS) {
+ OS << "Function: " << FName << ": ";
+ Profiles[FName].print(OS);
}
-/// \brief Dump all the function profiles found.
-void SampleProfileReader::dump() {
- for (StringMap<FunctionSamples>::const_iterator I = Profiles.begin(),
- E = Profiles.end();
- I != E; ++I)
- dumpFunctionProfile(I->getKey());
+/// \brief Dump all the function profiles found on stream \p OS.
+void SampleProfileReader::dump(raw_ostream &OS) {
+ for (const auto &I : Profiles)
+ dumpFunctionProfile(I.getKey(), OS);
}
/// \brief Load samples from a text file.
/// the expected format.
///
/// \returns true if the file was loaded successfully, false otherwise.
-bool SampleProfileReader::loadText() {
- ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
- MemoryBuffer::getFile(Filename);
- if (std::error_code EC = BufferOrErr.getError()) {
- std::string Msg(EC.message());
- M.getContext().diagnose(DiagnosticInfoSampleProfile(Filename.data(), Msg));
- return false;
- }
- MemoryBuffer &Buffer = *BufferOrErr.get();
- line_iterator LineIt(Buffer, /*SkipBlanks=*/true, '#');
+std::error_code SampleProfileReaderText::read() {
+ line_iterator LineIt(*Buffer, /*SkipBlanks=*/true, '#');
// Read the profile of each function. Since each function may be
// mentioned more than once, and we are collecting flat profiles,
// accumulate samples as we parse them.
Regex HeadRE("^([^0-9].*):([0-9]+):([0-9]+)$");
- Regex LineSample("^([0-9]+)\\.?([0-9]+)?: ([0-9]+)(.*)$");
+ Regex LineSampleRE("^([0-9]+)\\.?([0-9]+)?: ([0-9]+)(.*)$");
+ Regex CallSampleRE(" +([^0-9 ][^ ]*):([0-9]+)");
while (!LineIt.is_at_eof()) {
// Read the header of each function.
//
//
// The only requirement we place on the identifier, then, is that it
// should not begin with a number.
- SmallVector<StringRef, 3> Matches;
+ SmallVector<StringRef, 4> Matches;
if (!HeadRE.match(*LineIt, &Matches)) {
- reportParseError(LineIt.line_number(),
- "Expected 'mangled_name:NUM:NUM', found " + *LineIt);
- return false;
+ reportError(LineIt.line_number(),
+ "Expected 'mangled_name:NUM:NUM', found " + *LineIt);
+ return sampleprof_error::malformed;
}
assert(Matches.size() == 4);
StringRef FName = Matches[1];
// Now read the body. The body of the function ends when we reach
// EOF or when we see the start of the next function.
while (!LineIt.is_at_eof() && isdigit((*LineIt)[0])) {
- if (!LineSample.match(*LineIt, &Matches)) {
- reportParseError(
- LineIt.line_number(),
- "Expected 'NUM[.NUM]: NUM[ mangled_name:NUM]*', found " + *LineIt);
- return false;
+ if (!LineSampleRE.match(*LineIt, &Matches)) {
+ reportError(LineIt.line_number(),
+ "Expected 'NUM[.NUM]: NUM[ mangled_name:NUM]*', found " +
+ *LineIt);
+ return sampleprof_error::malformed;
}
assert(Matches.size() == 5);
unsigned LineOffset, NumSamples, Discriminator = 0;
Matches[2].getAsInteger(10, Discriminator);
Matches[3].getAsInteger(10, NumSamples);
- // FIXME: Handle called targets (in Matches[4]).
+ // If there are function calls in this line, generate a call sample
+ // entry for each call.
+ std::string CallsLine(Matches[4]);
+ while (CallsLine != "") {
+ SmallVector<StringRef, 3> CallSample;
+ if (!CallSampleRE.match(CallsLine, &CallSample)) {
+ reportError(LineIt.line_number(),
+ "Expected 'mangled_name:NUM', found " + CallsLine);
+ return sampleprof_error::malformed;
+ }
+ StringRef CalledFunction = CallSample[1];
+ unsigned CalledFunctionSamples;
+ CallSample[2].getAsInteger(10, CalledFunctionSamples);
+ FProfile.addCalledTargetSamples(LineOffset, Discriminator,
+ CalledFunction, CalledFunctionSamples);
+ CallsLine = CallSampleRE.sub("", CallsLine);
+ }
- // When dealing with instruction weights, we use the value
- // zero to indicate the absence of a sample. If we read an
- // actual zero from the profile file, return it as 1 to
- // avoid the confusion later on.
- if (NumSamples == 0)
- NumSamples = 1;
FProfile.addBodySamples(LineOffset, Discriminator, NumSamples);
++LineIt;
}
}
- return true;
+ return sampleprof_error::success;
+}
+
+template <typename T> ErrorOr<T> SampleProfileReaderBinary::readNumber() {
+ unsigned NumBytesRead = 0;
+ std::error_code EC;
+ uint64_t Val = decodeULEB128(Data, &NumBytesRead);
+
+ if (Val > std::numeric_limits<T>::max())
+ EC = sampleprof_error::malformed;
+ else if (Data + NumBytesRead > End)
+ EC = sampleprof_error::truncated;
+ else
+ EC = sampleprof_error::success;
+
+ if (EC) {
+ reportError(0, EC.message());
+ return EC;
+ }
+
+ Data += NumBytesRead;
+ return static_cast<T>(Val);
+}
+
+ErrorOr<StringRef> SampleProfileReaderBinary::readString() {
+ std::error_code EC;
+ StringRef Str(reinterpret_cast<const char *>(Data));
+ if (Data + Str.size() + 1 > End) {
+ EC = sampleprof_error::truncated;
+ reportError(0, EC.message());
+ return EC;
+ }
+
+ Data += Str.size() + 1;
+ return Str;
+}
+
+std::error_code SampleProfileReaderBinary::read() {
+ while (!at_eof()) {
+ auto FName(readString());
+ if (std::error_code EC = FName.getError())
+ return EC;
+
+ Profiles[*FName] = FunctionSamples();
+ FunctionSamples &FProfile = Profiles[*FName];
+
+ auto Val = readNumber<unsigned>();
+ if (std::error_code EC = Val.getError())
+ return EC;
+ FProfile.addTotalSamples(*Val);
+
+ Val = readNumber<unsigned>();
+ if (std::error_code EC = Val.getError())
+ return EC;
+ FProfile.addHeadSamples(*Val);
+
+ // Read the samples in the body.
+ auto NumRecords = readNumber<unsigned>();
+ if (std::error_code EC = NumRecords.getError())
+ return EC;
+ for (unsigned I = 0; I < *NumRecords; ++I) {
+ 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 NumSamples = readNumber<uint64_t>();
+ if (std::error_code EC = NumSamples.getError())
+ return EC;
+
+ auto NumCalls = readNumber<unsigned>();
+ if (std::error_code EC = NumCalls.getError())
+ return EC;
+
+ for (unsigned J = 0; J < *NumCalls; ++J) {
+ auto CalledFunction(readString());
+ 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, *Discriminator,
+ *CalledFunction,
+ *CalledFunctionSamples);
+ }
+
+ FProfile.addBodySamples(*LineOffset, *Discriminator, *NumSamples);
+ }
+ }
+
+ return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderBinary::readHeader() {
+ Data = reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
+ End = Data + Buffer->getBufferSize();
+
+ // Read and check the magic identifier.
+ auto Magic = readNumber<uint64_t>();
+ if (std::error_code EC = Magic.getError())
+ return EC;
+ else if (*Magic != SPMagic())
+ return sampleprof_error::bad_magic;
+
+ // Read the version number.
+ auto Version = readNumber<uint64_t>();
+ if (std::error_code EC = Version.getError())
+ return EC;
+ else if (*Version != SPVersion())
+ return sampleprof_error::unsupported_version;
+
+ return sampleprof_error::success;
+}
+
+bool SampleProfileReaderBinary::hasFormat(const MemoryBuffer &Buffer) {
+ const uint8_t *Data =
+ reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
+ uint64_t Magic = decodeULEB128(Data);
+ return Magic == SPMagic();
+}
+
+bool SourceInfo::operator<(const SourceInfo &P) const {
+ if (Line != P.Line)
+ return Line < P.Line;
+ if (StartLine != P.StartLine)
+ return StartLine < P.StartLine;
+ if (Discriminator != P.Discriminator)
+ return Discriminator < P.Discriminator;
+ return FuncName < P.FuncName;
}
-/// \brief Load execution samples from a file.
+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::V704)
+ 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(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;
+
+ SourceStack Stack;
+ for (uint32_t I = 0; I < NumFunctions; ++I)
+ if (std::error_code EC = readOneFunctionProfile(Stack, true))
+ return EC;
+
+ return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderGCC::addSourceCount(StringRef Name,
+ const SourceStack &Src,
+ uint64_t Count) {
+ if (Src.size() == 0 || Src[0].Malformed())
+ return sampleprof_error::malformed;
+ FunctionSamples &FProfile = Profiles[Name];
+ FProfile.addTotalSamples(Count);
+ // FIXME(dnovillo) - Properly update inline stack for FnName.
+ FProfile.addBodySamples(Src[0].Line, Src[0].Discriminator, Count);
+ return sampleprof_error::success;
+}
+
+
+std::error_code
+SampleProfileReaderGCC::readOneFunctionProfile(const SourceStack &Stack,
+ bool Update) {
+ uint64_t HeadCount = 0;
+ if (Stack.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;
+
+ if (Stack.size() == 0) {
+ FunctionSamples &FProfile = Profiles[Name];
+ FProfile.addHeadSamples(HeadCount);
+ if (FProfile.getTotalSamples() > 0)
+ Update = false;
+ }
+
+ 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;
+
+ SourceInfo Info(Name, "", "", 0, Offset >> 16, Offset & 0xffff);
+ SourceStack NewStack;
+ NewStack.push_back(Info);
+ NewStack.insert(NewStack.end(), Stack.begin(), Stack.end());
+ if (Update)
+ addSourceCount(NewStack[NewStack.size() - 1].FuncName, NewStack, Count);
+
+ 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) {
+ FunctionSamples &TargetProfile = Profiles[TargetName];
+ TargetProfile.addBodySamples(NewStack[0].Line,
+ NewStack[0].Discriminator, TargetCount);
+ }
+ }
+ }
+
+ 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;
+ SourceInfo Info(Name, "", "", 0, Offset >> 16, Offset & 0xffff);
+ SourceStack NewStack;
+ NewStack.push_back(Info);
+ NewStack.insert(NewStack.end(), Stack.begin(), Stack.end());
+ if (std::error_code EC = readOneFunctionProfile(NewStack, Update))
+ return EC;
+ }
+
+ return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderGCC::readModuleGroup() {
+ // FIXME(dnovillo) - Module support still not implemented.
+ return sampleprof_error::not_implemented;
+}
+
+std::error_code SampleProfileReaderGCC::readWorkingSet() {
+ // FIXME(dnovillo) - Working sets still not implemented.
+ return sampleprof_error::not_implemented;
+}
+
+
+/// \brief 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::read() {
+ // Read the string table.
+ if (std::error_code EC = readNameTable())
+ return EC;
+
+ // Read the source profile.
+ if (std::error_code EC = readFunctionProfiles())
+ return EC;
+
+ // FIXME(dnovillo) - Module groups and working set support are not
+ // yet implemented.
+#if 0
+ // Read the module group file.
+ if (std::error_code EC = readModuleGroup())
+ return EC;
+
+ // Read the working set.
+ if (std::error_code EC = readWorkingSet())
+ return EC;
+#endif
+
+ return sampleprof_error::success;
+}
+
+bool SampleProfileReaderGCC::hasFormat(const MemoryBuffer &Buffer) {
+ StringRef Magic(reinterpret_cast<const char *>(Buffer.getBufferStart()));
+ return Magic == "adcg*704";
+}
+
+/// \brief 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(std::string Filename) {
+ auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(Filename);
+ if (std::error_code EC = BufferOrErr.getError())
+ return EC;
+ auto Buffer = std::move(BufferOrErr.get());
+
+ // Sanity check the file.
+ if (Buffer->getBufferSize() > std::numeric_limits<unsigned>::max())
+ return sampleprof_error::too_large;
+
+ return std::move(Buffer);
+}
+
+/// \brief Create a sample profile reader based on the format of the input file.
+///
+/// \param Filename The file to open.
+///
+/// \param Reader The reader to instantiate according to \p Filename's format.
///
-/// This function examines the header of the given file to determine
-/// whether to use the text or the bitcode loader.
-bool SampleProfileReader::load() {
- // TODO Actually detect the file format.
- return loadText();
+/// \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<SampleProfileReader>>
+SampleProfileReader::create(StringRef Filename, LLVMContext &C) {
+ auto BufferOrError = setupMemoryBuffer(Filename);
+ if (std::error_code EC = BufferOrError.getError())
+ return EC;
+
+ auto Buffer = std::move(BufferOrError.get());
+ std::unique_ptr<SampleProfileReader> Reader;
+ if (SampleProfileReaderBinary::hasFormat(*Buffer))
+ Reader.reset(new SampleProfileReaderBinary(std::move(Buffer), C));
+ else if (SampleProfileReaderGCC::hasFormat(*Buffer))
+ Reader.reset(new SampleProfileReaderGCC(std::move(Buffer), C));
+ else
+ Reader.reset(new SampleProfileReaderText(std::move(Buffer), C));
+
+ if (std::error_code EC = Reader->readHeader())
+ return EC;
+
+ return std::move(Reader);
}