diff --git a/gcov.cc b/gcov.cc index 811cbf0..f16437b 100644 --- a/gcov.cc +++ b/gcov.cc @@ -29,6 +29,7 @@ ABSL_FLAG(uint64_t, gcov_version, 0x3430372a, "Gcov version number."); +const uint32 GCOV_TAG_AFDO_SUMMARY = 0xa8000000; const uint32 GCOV_TAG_AFDO_FILE_NAMES = 0xaa000000; const uint32 GCOV_TAG_AFDO_FUNCTION = 0xac000000; const uint32 GCOV_TAG_MODULE_GROUPING = 0xae000000; @@ -149,7 +150,7 @@ void gcov_write_string(const char *string) { if (string) { length = strlen(string); - if (absl::GetFlag(FLAGS_gcov_version) == 2) { + if (absl::GetFlag(FLAGS_gcov_version) >= 2) { // Length includes the terminating 0 and is saved in bytes. alloc = length + 1; char *byte_buffer = gcov_write_bytes(4 + alloc); @@ -229,7 +230,7 @@ const char * gcov_read_string(void) { return 0; } - if (absl::GetFlag(FLAGS_gcov_version) == 2) { + if (absl::GetFlag(FLAGS_gcov_version) >= 2) { return gcov_read_bytes (length); } else { diff --git a/gcov.h b/gcov.h index 562d603..1c7947b 100644 --- a/gcov.h +++ b/gcov.h @@ -20,6 +20,7 @@ #include "base/common.h" #include "third_party/abseil/absl/flags/declare.h" +extern const uint32 GCOV_TAG_AFDO_SUMMARY; extern const uint32 GCOV_TAG_AFDO_FILE_NAMES; extern const uint32 GCOV_TAG_AFDO_FUNCTION; extern const uint32 GCOV_TAG_MODULE_GROUPING; diff --git a/profile_reader.cc b/profile_reader.cc index 8097a50..d39d576 100644 --- a/profile_reader.cc +++ b/profile_reader.cc @@ -87,6 +87,23 @@ void AutoFDOProfileReader::ReadSymbolProfile(const SourceStack &stack, } } +void AutoFDOProfileReader::ReadSummary() { + if (absl::GetFlag(FLAGS_gcov_version) >= 3) { + CHECK_EQ(gcov_read_unsigned(), GCOV_TAG_AFDO_SUMMARY); + gcov_read_counter(); // Total count + gcov_read_counter(); // Max count + gcov_read_counter(); // Max function count + gcov_read_counter(); // Num counts + gcov_read_counter(); // Num functions + unsigned num = gcov_read_counter(); + for (unsigned i = 0; i < num; i++) { + gcov_read_unsigned(); // Cutoff + gcov_read_counter(); // Min count + gcov_read_counter(); // Num counts >= min count + } + } +} + void AutoFDOProfileReader::ReadNameTable() { CHECK_EQ(gcov_read_unsigned(), GCOV_TAG_AFDO_FILE_NAMES); gcov_read_unsigned(); @@ -115,6 +132,7 @@ bool AutoFDOProfileReader::ReadFromFile(const std::string &output_file) { absl::SetFlag(&FLAGS_gcov_version, gcov_read_unsigned()); gcov_read_unsigned(); + ReadSummary(); ReadNameTable(); ReadFunctionProfile(); ReadModuleGroup(); diff --git a/profile_reader.h b/profile_reader.h index 02a09f0..31502e6 100644 --- a/profile_reader.h +++ b/profile_reader.h @@ -54,6 +54,7 @@ class AutoFDOProfileReader : public ProfileReader { // where symbol_map was built purely from profile thus alias symbol info // is not available. In that case, we should always update the symbol. void ReadSymbolProfile(const SourceStack &stack, bool update); + void ReadSummary(); void ReadNameTable(); SymbolMap *symbol_map_; diff --git a/profile_writer.cc b/profile_writer.cc index 875ed06..52f4817 100644 --- a/profile_writer.cc +++ b/profile_writer.cc @@ -155,6 +155,26 @@ void AutoFDOProfileWriter::WriteFunctionProfile() { StringTableUpdater::Update(*symbol_map_, &string_index_map); + // Write out the GCOV_TAG_AFDO_SUMMARY section. + if (absl::GetFlag(FLAGS_gcov_version) >= 3) { + assert(summary != nullptr); + ProfileSummaryInformation info = ProfileSummaryComputer::Compute( + *symbol_map_, {std::begin(ProfileSummaryInformation::default_cutoffs), + std::end(ProfileSummaryInformation::default_cutoffs)}); + gcov_write_unsigned(GCOV_TAG_AFDO_SUMMARY); + gcov_write_counter(info.total_count_); + gcov_write_counter(info.max_count_); + gcov_write_counter(info.max_function_count_); + gcov_write_counter(info.num_counts_); + gcov_write_counter(info.num_functions_); + gcov_write_counter(info.detailed_summaries_.size()); + for (const auto &detailed_summary : info.detailed_summaries_) { + gcov_write_unsigned(detailed_summary.cutoff_); + gcov_write_counter(detailed_summary.min_count_); + gcov_write_counter(detailed_summary.num_counts_); + } + } + for (auto &name_index : string_index_map) { name_index.second = current_name_index++; length_4bytes += (name_index.first.size() @@ -227,6 +247,64 @@ bool AutoFDOProfileWriter::WriteToFile(const std::string &output_filename) { return true; } +ProfileSummaryComputer::ProfileSummaryComputer() + : cutoffs_{std::begin(ProfileSummaryInformation::default_cutoffs), + std::end(ProfileSummaryInformation::default_cutoffs)} {} + +ProfileSummaryComputer::ProfileSummaryComputer(std::vector cutoffs) + : cutoffs_{std::move(cutoffs)} {} + +void ProfileSummaryComputer::VisitTopSymbol(const std::string &name, + const Symbol *node) { + info_.num_functions_++; + info_.max_function_count_ = + std::max(info_.max_function_count_, node->head_count); +} + +void ProfileSummaryComputer::Visit(const Symbol *node) { + // There is a slight difference against the values computed by + // SampleProfileSummaryBuilder/LLVMProfileBuilder as it represents + // lineno:discriminator pairs as 16:32 bits. This causes line numbers >= + // UINT16_MAX to be counted incorrectly (see GetLineNumberFromOffset in + // source_info.h) as they collide with line numbers < UINT16_MAX. This issue + // is completely avoided here by just not using the offset info at all. + for (auto &pos_count : node->pos_counts) { + uint64_t count = pos_count.second.count; + info_.total_count_ += count; + info_.max_count_ = std::max(info_.max_count_, count); + info_.num_counts_++; + count_frequencies_[count]++; + } +} + +void ProfileSummaryComputer::ComputeDetailedSummary() { + auto iter = count_frequencies_.begin(); + auto end = count_frequencies_.end(); + + uint32_t counts_seen = 0; + uint64_t curr_sum = 0; + uint64_t count = 0; + + for (const uint32_t cutoff : cutoffs_) { + assert(cutoff <= 999'999); + constexpr int scale = 1'000'000; + using uint128_t = unsigned __int128; + uint128_t desired_count = info_.total_count_; + desired_count = desired_count * uint128_t(cutoff); + desired_count = desired_count / uint128_t(scale); + assert(desired_count <= info_.total_count_); + while (curr_sum < desired_count && iter != end) { + count = iter->first; + uint32_t freq = iter->second; + curr_sum += (count * freq); + counts_seen += freq; + iter++; + } + assert(curr_sum >= desired_count); + info_.detailed_summaries_.push_back({cutoff, count, counts_seen}); + } +} + // Debugging support. ProfileDumper emits a detailed dump of the contents // of the input profile. class ProfileDumper : public SymbolTraverser { diff --git a/profile_writer.h b/profile_writer.h index 2557b08..d86a760 100644 --- a/profile_writer.h +++ b/profile_writer.h @@ -182,6 +182,68 @@ class StringTableUpdater: public SymbolTraverser { StringIndexMap *map_; }; +class ProfileSummaryInformation { +public: + static constexpr std::array default_cutoffs = { + 10000, 100000, 200000, 300000, 400000, 500000, 600000, 700000, + 800000, 900000, 950000, 990000, 999000, 999900, 999990, 999999}; + + // The detailed summary is a histogram-based calculation of the minimum + // execution count required to belong to a certain set of percentile of + // counts. + struct DetailedSummary { + // The percentile that this represents (multiplied by 1,000,000). + uint32_t cutoff_{}; + // The minimum execution count required to belong to this percentile. + uint64_t min_count_{}; + // The number of samples which belong to this percentile. + uint64_t num_counts_{}; + }; + + // The sum of execution counts of all samples. + uint64_t total_count_{}; + // The maximum individual count. + uint64_t max_count_{}; + // The maximum head count across all functions. + uint64_t max_function_count_{}; + // The number of lines that have samples. + uint64_t num_counts_{}; + // The number of functions that have samples. + uint64_t num_functions_{}; + // The percentile threshold information. + std::vector detailed_summaries_{}; +}; + +class ProfileSummaryComputer : public SymbolTraverser { +public: + // This type is neither copyable nor movable. + ProfileSummaryComputer(const ProfileSummaryComputer &) = delete; + ProfileSummaryComputer &operator=(const ProfileSummaryComputer &) = delete; + + ProfileSummaryComputer(); + ProfileSummaryComputer(std::vector cutoffs); + + static ProfileSummaryInformation Compute(const SymbolMap &symbol_map, + std::vector cutoffs) { + ProfileSummaryComputer computer(std::move(cutoffs)); + computer.Start(symbol_map); + computer.ComputeDetailedSummary(); + return std::move(computer.info_); + } + +protected: + void VisitTopSymbol(const std::string &name, const Symbol *node) override; + void Visit(const Symbol *node) override; + +private: + ProfileSummaryInformation info_{}; + // Sorted map of frequencies - used to compute the histogram. + std::map> count_frequencies_; + std::vector cutoffs_; + + void ComputeDetailedSummary(); +}; + } // namespace devtools_crosstool_autofdo #endif // AUTOFDO_PROFILE_WRITER_H_