From 81e66e3d3095823424550879b23a703c8e9adf4b Mon Sep 17 00:00:00 2001 From: Dhruv Chawla Date: Mon, 25 Aug 2025 03:29:18 -0700 Subject: [PATCH 1/4] [RFC] Add profile summary information to GCOV This patch adds supports for writing out profile summaries and histogram-based percentile information for samples to the GCOV profile, in a similar manner to how this information is written out for LLVM profiles. It also bumps the GCOV version to 3. --- gcov.cc | 7 +++++-- gcov.h | 1 + profile_reader.cc | 18 ++++++++++++++++++ profile_reader.h | 1 + profile_writer.cc | 27 +++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/gcov.cc b/gcov.cc index 811cbf0..d9f1aa1 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,8 @@ 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 || + absl::GetFlag(FLAGS_gcov_version) == 3) { // Length includes the terminating 0 and is saved in bytes. alloc = length + 1; char *byte_buffer = gcov_write_bytes(4 + alloc); @@ -229,7 +231,8 @@ const char * gcov_read_string(void) { return 0; } - if (absl::GetFlag(FLAGS_gcov_version) == 2) { + if (absl::GetFlag(FLAGS_gcov_version) == 2 || + absl::GetFlag(FLAGS_gcov_version) == 3) { 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..02a1563 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 cutoffs + } + } +} + 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..c4a1247 100644 --- a/profile_writer.cc +++ b/profile_writer.cc @@ -26,6 +26,10 @@ #include "third_party/abseil/absl/flags/flag.h" #include "third_party/abseil/absl/strings/str_format.h" +#include "llvm_profile_writer.h" +#include "llvm/ProfileData/SampleProf.h" +#include "llvm/ProfileData/ProfileCommon.h" + // sizeof(gcov_unsigned_t) #define SIZEOF_UNSIGNED 4 @@ -155,6 +159,29 @@ void AutoFDOProfileWriter::WriteFunctionProfile() { StringTableUpdater::Update(*symbol_map_, &string_index_map); + LLVMProfileBuilder builder(string_index_map); + const llvm::SampleProfileMap &profiles = + builder.ConvertProfiles(*symbol_map_); + llvm::SampleProfileSummaryBuilder summary_builder(llvm::ProfileSummaryBuilder::DefaultCutoffs); + std::unique_ptr summary = summary_builder.computeSummaryForProfiles(profiles); + + // Write out the GCOV_TAG_AFDO_SUMMARY section. + if (absl::GetFlag(FLAGS_gcov_version) >= 3) { + assert(summary != nullptr); + gcov_write_unsigned(GCOV_TAG_AFDO_SUMMARY); + gcov_write_counter(summary->getTotalCount()); + gcov_write_counter(summary->getMaxCount()); + gcov_write_counter(summary->getMaxFunctionCount()); + gcov_write_counter(summary->getNumCounts()); + gcov_write_counter(summary->getNumFunctions()); + gcov_write_counter(summary->getDetailedSummary().size()); + for (const auto &detailed_summary : summary->getDetailedSummary()) { + gcov_write_unsigned(detailed_summary.Cutoff); + gcov_write_counter(detailed_summary.MinCount); + gcov_write_counter(detailed_summary.NumCounts); + } + } + for (auto &name_index : string_index_map) { name_index.second = current_name_index++; length_4bytes += (name_index.first.size() From 0c4a9c551ab3206c05a35b12d8c8e11751791139 Mon Sep 17 00:00:00 2001 From: Dhruv Chawla Date: Mon, 27 Oct 2025 00:19:50 -0700 Subject: [PATCH 2/4] Simplify version comparison --- gcov.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gcov.cc b/gcov.cc index d9f1aa1..f16437b 100644 --- a/gcov.cc +++ b/gcov.cc @@ -150,8 +150,7 @@ void gcov_write_string(const char *string) { if (string) { length = strlen(string); - if (absl::GetFlag(FLAGS_gcov_version) == 2 || - absl::GetFlag(FLAGS_gcov_version) == 3) { + 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); @@ -231,8 +230,7 @@ const char * gcov_read_string(void) { return 0; } - if (absl::GetFlag(FLAGS_gcov_version) == 2 || - absl::GetFlag(FLAGS_gcov_version) == 3) { + if (absl::GetFlag(FLAGS_gcov_version) >= 2) { return gcov_read_bytes (length); } else { From 9627fd23228afac7ee5e58909d98f6fe99124149 Mon Sep 17 00:00:00 2001 From: Dhruv Chawla Date: Wed, 12 Nov 2025 00:55:17 -0800 Subject: [PATCH 3/4] Implement summary calculation as standalone visitor --- profile_writer.cc | 90 +++++++++++++++++++++++++++++++++++++---------- profile_writer.h | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 19 deletions(-) diff --git a/profile_writer.cc b/profile_writer.cc index c4a1247..aa1686d 100644 --- a/profile_writer.cc +++ b/profile_writer.cc @@ -26,10 +26,6 @@ #include "third_party/abseil/absl/flags/flag.h" #include "third_party/abseil/absl/strings/str_format.h" -#include "llvm_profile_writer.h" -#include "llvm/ProfileData/SampleProf.h" -#include "llvm/ProfileData/ProfileCommon.h" - // sizeof(gcov_unsigned_t) #define SIZEOF_UNSIGNED 4 @@ -159,26 +155,24 @@ void AutoFDOProfileWriter::WriteFunctionProfile() { StringTableUpdater::Update(*symbol_map_, &string_index_map); - LLVMProfileBuilder builder(string_index_map); - const llvm::SampleProfileMap &profiles = - builder.ConvertProfiles(*symbol_map_); - llvm::SampleProfileSummaryBuilder summary_builder(llvm::ProfileSummaryBuilder::DefaultCutoffs); - std::unique_ptr summary = summary_builder.computeSummaryForProfiles(profiles); + ProfileSummaryInformation info = ProfileSummaryComputer::Compute( + *symbol_map_, {std::begin(ProfileSummaryInformation::default_cutoffs), + std::end(ProfileSummaryInformation::default_cutoffs)}); // Write out the GCOV_TAG_AFDO_SUMMARY section. if (absl::GetFlag(FLAGS_gcov_version) >= 3) { assert(summary != nullptr); gcov_write_unsigned(GCOV_TAG_AFDO_SUMMARY); - gcov_write_counter(summary->getTotalCount()); - gcov_write_counter(summary->getMaxCount()); - gcov_write_counter(summary->getMaxFunctionCount()); - gcov_write_counter(summary->getNumCounts()); - gcov_write_counter(summary->getNumFunctions()); - gcov_write_counter(summary->getDetailedSummary().size()); - for (const auto &detailed_summary : summary->getDetailedSummary()) { - gcov_write_unsigned(detailed_summary.Cutoff); - gcov_write_counter(detailed_summary.MinCount); - gcov_write_counter(detailed_summary.NumCounts); + 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_); } } @@ -254,6 +248,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_ From aac92ca12b9d95ca8766c8f91e119710d7c307b8 Mon Sep 17 00:00:00 2001 From: Dhruv Chawla Date: Tue, 6 Jan 2026 21:16:49 -0800 Subject: [PATCH 4/4] Address review comments --- profile_reader.cc | 2 +- profile_writer.cc | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/profile_reader.cc b/profile_reader.cc index 02a1563..d39d576 100644 --- a/profile_reader.cc +++ b/profile_reader.cc @@ -99,7 +99,7 @@ void AutoFDOProfileReader::ReadSummary() { for (unsigned i = 0; i < num; i++) { gcov_read_unsigned(); // Cutoff gcov_read_counter(); // Min count - gcov_read_counter(); // Num cutoffs + gcov_read_counter(); // Num counts >= min count } } } diff --git a/profile_writer.cc b/profile_writer.cc index aa1686d..52f4817 100644 --- a/profile_writer.cc +++ b/profile_writer.cc @@ -155,13 +155,12 @@ void AutoFDOProfileWriter::WriteFunctionProfile() { StringTableUpdater::Update(*symbol_map_, &string_index_map); - ProfileSummaryInformation info = ProfileSummaryComputer::Compute( - *symbol_map_, {std::begin(ProfileSummaryInformation::default_cutoffs), - std::end(ProfileSummaryInformation::default_cutoffs)}); - // 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_);