Translate correlation builders into the shared GCOV profile model. The
exporter assigns stable names and identifiers, emits inline trees and
indirect targets, and constructs profile summaries.
afdo_tools/ChangeLog:
* src/autofdo_gcov_export.cc: New file.
* src/autofdo_gcov_export.h: New file.
Signed-off-by: Kugan Vivekanandarajah <[email protected]>
Co-authored-by: Dhruv Chawla <[email protected]>
Co-authored-by: Rahul Shinde <[email protected]>
---
afdo_tools/src/autofdo_gcov_export.cc | 343 ++++++++++++++++++++++++++
afdo_tools/src/autofdo_gcov_export.h | 46 ++++
2 files changed, 389 insertions(+)
create mode 100644 afdo_tools/src/autofdo_gcov_export.cc
create mode 100644 afdo_tools/src/autofdo_gcov_export.h
diff --git a/afdo_tools/src/autofdo_gcov_export.cc
b/afdo_tools/src/autofdo_gcov_export.cc
new file mode 100644
index 00000000000..3864ba24c2a
--- /dev/null
+++ b/afdo_tools/src/autofdo_gcov_export.cc
@@ -0,0 +1,343 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "autofdo_gcov_export.h"
+
+#include "autofdo_model.h"
+#include "gcov_discriminator_encoding.h"
+#include "gcov_summary.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace autofdo {
+
+namespace {
+
+using gcov_name_index_map = std::unordered_map<std::string, uint32_t>;
+using gcov_identity_index_map = std::map<gcov::symbol_identity, uint32_t>;
+
+/* Walk BUILDER and its callsites, gathering into IDENTITIES every function
+ the string table must name and into TARGET_NAMES every call target. */
+void
+collect_export_identities (const function_builder &builder,
+ std::set<gcov::symbol_identity> &identities,
+ std::set<std::string> &target_names)
+{
+ if (!builder.name.empty ())
+ identities.insert ({builder.name, builder.file_path});
+ for (const auto &kv : builder.position_targets ())
+ for (const auto &target : kv.second)
+ if (!dwarf_debug::string_empty (target.first))
+ target_names.insert (dwarf_debug::string_value (target.first));
+ for (const auto &kv : builder.callsites ())
+ {
+ const function_builder &child = *kv.second;
+ if (!dwarf_debug::string_empty (kv.first.function_name))
+ identities.insert (
+ {dwarf_debug::string_value (kv.first.function_name),
+ child.file_path});
+ collect_export_identities (child, identities, target_names);
+ }
+}
+
+/* Split PACKED (relative line, discriminator) into LOCATION's two GCOV
+ offset words. */
+void
+set_gcov_location_offset (gcov::gcov_location &location, uint64_t packed)
+{
+ const uint32_t encoded = gcov_compress_offset (packed);
+ location.offset = encoded >> 16;
+ location.discriminator = encoded & 0xffff;
+}
+
+/* Turn BUILDER's positions and callsites into GCOV locations in OUT, with
+ names resolved through IDENTITY_TO_IDX and NAME_TO_IDX. Returns the total
+ count of the subtree, which the caller needs for the enclosing entry. */
+uint64_t
+build_gcov_locations (const function_builder &builder,
+ const gcov_identity_index_map &identity_to_idx,
+ const gcov_name_index_map &name_to_idx,
+ std::vector<std::unique_ptr<gcov::gcov_location>> &out)
+{
+ uint64_t subtree_total = 0;
+ const function_builder::position_count_map &position_counts
+ = builder.position_counts ();
+ const function_builder::position_target_map &position_targets
+ = builder.position_targets ();
+ for (const auto &kv : position_counts)
+ {
+ const uint64_t key = kv.first;
+ const uint64_t count = kv.second;
+ const auto targets_it = position_targets.find (key);
+ const bool has_targets
+ = targets_it != position_targets.end () && !targets_it->second.empty ();
+ const uint32_t relative_line = gcov_rel_line_from_packed (key);
+ /* Drop positions that GCOV cannot encode rather than wrap their line.
*/
+ if (!gcov_offset_fields_representable (relative_line))
+ continue;
+
+ std::unique_ptr<gcov::gcov_location> location;
+ if (!has_targets)
+ location = std::make_unique<gcov::gcov_location_normal> (count);
+ else
+ {
+ std::vector<std::pair<uint32_t, uint64_t>> targets;
+ targets.reserve (targets_it->second.size ());
+ for (const auto &target : targets_it->second)
+ {
+ if (dwarf_debug::string_empty (target.first)
+ || target.second == 0)
+ continue;
+ const std::string target_name
+ = dwarf_debug::string_value (target.first);
+ const auto name_it = name_to_idx.find (target_name);
+ if (name_it == name_to_idx.end () || name_it->second == 0)
+ {
+ std::fprintf (
+ stderr,
+ "afdo_create: skip unresolved export name '%.*s'\n",
+ static_cast<int> (target_name.size ()),
+ target_name.data ());
+ continue;
+ }
+ targets.push_back ({name_it->second, target.second});
+ }
+ if (targets.size () > 1)
+ std::sort (
+ targets.begin (),
+ targets.end (),
+ [] (const auto &a, const auto &b) { return a.first < b.first; });
+ if (targets.empty ())
+ location = std::make_unique<gcov::gcov_location_normal> (count);
+ else
+ {
+ auto call = std::make_unique<gcov::gcov_location_call> (count);
+ call->callee_map.reserve (targets.size ());
+ for (const auto &target : targets)
+ call->callee_map[static_cast<uint64_t> (target.first)]
+ = target.second;
+ location = std::move (call);
+ }
+ }
+
+ set_gcov_location_offset (*location, key);
+ subtree_total += location->total_count ();
+ out.emplace_back (std::move (location));
+ }
+
+ for (const auto &kv : builder.callsites ())
+ {
+ const function_builder &child = *kv.second;
+ const uint32_t callsite_line = kv.first.call_line;
+ /* Without a usable offset the callsite would land on the function
+ entry line, putting the inlined body in the wrong place. */
+ if (builder.start_line == 0 || callsite_line < builder.start_line)
+ continue;
+
+ const uint32_t offset = callsite_line - builder.start_line;
+ const uint32_t discriminator
+ = kv.first.call_disc != 0
+ ? kv.first.call_disc
+ : get_base_discriminator (child.discriminator);
+ if (!gcov_offset_fields_representable (offset))
+ continue;
+
+ auto inlined = std::make_unique<gcov::gcov_location_inline> ();
+ set_gcov_location_offset
+ (*inlined, gcov_pack_line_disc (offset, discriminator));
+
+ std::string callee_name;
+ if (!dwarf_debug::string_empty (kv.first.function_name))
+ callee_name = dwarf_debug::string_value (kv.first.function_name);
+ else
+ callee_name = child.name;
+ if (callee_name.empty ())
+ continue;
+
+ const auto identity_it
+ = identity_to_idx.find ({callee_name, child.file_path});
+ if (identity_it == identity_to_idx.end () || identity_it->second == 0)
+ {
+ std::fprintf (
+ stderr,
+ "afdo_create: skip unresolved inline export name '%.*s'\n",
+ static_cast<int> (callee_name.size ()),
+ callee_name.data ());
+ continue;
+ }
+
+ gcov::gcov_function &symbol = inlined->inlined_sym;
+ symbol.id = identity_it->second;
+ symbol.desc.name_index = symbol.id;
+ symbol.timestamp = 0;
+ symbol.total_count = build_gcov_locations (
+ child, identity_to_idx, name_to_idx, symbol.locations);
+ subtree_total += inlined->total_count ();
+ out.emplace_back (std::move (inlined));
+ }
+ return subtree_total;
+}
+
+} /* anonymous namespace. */
+
+void
+gcov_profile_exporter::export_profile (const gcov_export_functions &functions,
+ gcov::gcov_profile &out)
+{
+ /* Inline identities retain their file. Indirect targets have no file
+ information and get a separate name-only entry. */
+ std::set<gcov::symbol_identity> extra_identities;
+ std::set<std::string> target_names;
+ for (const auto &entry : functions)
+ {
+ function_builder &builder = *entry.second->builder;
+ collect_export_identities (builder, extra_identities, target_names);
+ }
+ for (const std::string &name : target_names)
+ extra_identities.insert ({name, std::string ()});
+ for (const auto &entry : functions)
+ {
+ const function_builder *builder = entry.second->builder.get ();
+ extra_identities.erase ({entry.first, builder->file_path});
+ }
+
+ std::vector<std::string> function_names;
+ function_names.reserve (functions.size () + extra_identities.size () + 1);
+ function_names.push_back ("");
+ std::vector<std::string> symbol_paths;
+ symbol_paths.reserve (functions.size () + extra_identities.size () + 1);
+ symbol_paths.push_back ("");
+ std::vector<uint32_t> emit_name_ids (functions.size (), 0);
+ for (size_t i = 0; i < functions.size (); ++i)
+ {
+ const std::string &mangled = functions[i].first;
+ if (mangled.empty ())
+ continue;
+ function_names.push_back (mangled);
+ const function_builder *builder = functions[i].second->builder.get ();
+ symbol_paths.push_back (builder->file_path);
+ emit_name_ids[i] = static_cast<uint32_t> (function_names.size () - 1);
+ }
+ for (const auto &identity : extra_identities)
+ {
+ if (identity.name.empty ())
+ continue;
+ function_names.push_back (identity.name);
+ symbol_paths.push_back (identity.file);
+ }
+
+ gcov_identity_index_map identity_to_idx;
+ gcov_name_index_map name_to_idx;
+ name_to_idx.reserve (function_names.size ());
+ for (size_t i = 1; i < function_names.size (); ++i)
+ {
+ const uint32_t idx = static_cast<uint32_t> (i);
+ identity_to_idx.emplace (
+ gcov::symbol_identity{function_names[i], symbol_paths[i]}, idx);
+ /* Prefer the explicit name-only entry for targets without a file. */
+ if (symbol_paths[i].empty ())
+ name_to_idx[function_names[i]] = idx;
+ else
+ name_to_idx.emplace (function_names[i], idx);
+ }
+
+ std::vector<std::string> file_names;
+ std::vector<uint32_t> file_name_map;
+ gcov::gcov_profile::build_file_name_table (symbol_paths, file_names,
+ file_name_map);
+
+ std::vector<gcov::gcov_function> gcov_functions;
+ gcov_functions.reserve (functions.size ());
+ for (size_t i = 0; i < functions.size (); ++i)
+ {
+ const uint32_t id = emit_name_ids[i];
+ if (id == 0)
+ continue;
+ function_profile *func = functions[i].second;
+ const function_builder *builder = func->builder.get ();
+
+ gcov::gcov_function fn;
+ fn.id = id;
+ fn.desc.name_index = id;
+ const uint32_t fidx = file_name_map[id];
+ fn.desc.file_index
+ = (fidx < file_names.size ()) ? fidx : gcov::INVALID_INDEX;
+ fn.head_count = func->head_count;
+ fn.timestamp = func->timestamp;
+ fn.total_count = build_gcov_locations (
+ *builder, identity_to_idx, name_to_idx, fn.locations);
+ gcov_functions.push_back (std::move (fn));
+ }
+
+ gcov::gcov_summary summary;
+ gcov::compute_summary (gcov_functions, summary);
+ out.set_names (std::move (file_names), std::move (function_names),
+ std::move (file_name_map));
+ out.set_summary (std::move (summary));
+ out.set_functions (std::move (gcov_functions));
+}
+
+/* Finalize every function profile and write them all into OUT. */
+void
+profile_builder::build_gcov_profile (gcov::gcov_profile &out)
+{
+ std::vector<std::pair<std::string, function_profile *>> emit;
+ emit.reserve (function_profiles.size ());
+ for (auto &kv : function_profiles)
+ {
+ function_profile &func = kv.second;
+ if (!func.builder)
+ continue;
+ /* Skip zero-mass profiles (below emit threshold). */
+ if (func.total_samples == 0)
+ continue;
+ const std::string &mangled = kv.first.first;
+ emit.push_back ({mangled, &func});
+ }
+ /* Deterministic GCOV order; one profile per (name, start_addr) already. */
+ std::sort (emit.begin (), emit.end (), [] (const auto &a, const auto &b)
+ {
+ if (a.first != b.first)
+ return a.first < b.first;
+ const std::string &fa = a.second->builder->file_path;
+ const std::string &fb = b.second->builder->file_path;
+ if (fa != fb)
+ return fa < fb;
+ return a.second->start_addr < b.second->start_addr;
+ });
+
+ /* Materialize callsite entry counts before name collection, summary,
+ and serialization so all three see the same totals. */
+ for (auto &entry : emit)
+ {
+ function_builder *builder = entry.second->builder.get ();
+ builder->materialize_callsite_entry_counts ();
+ entry.second->total_samples = builder->compute_total_count ();
+ }
+
+ gcov_profile_exporter::export_profile (emit, out);
+}
+
+} /* namespace autofdo. */
diff --git a/afdo_tools/src/autofdo_gcov_export.h
b/afdo_tools/src/autofdo_gcov_export.h
new file mode 100644
index 00000000000..e238c272257
--- /dev/null
+++ b/afdo_tools/src/autofdo_gcov_export.h
@@ -0,0 +1,46 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_AUTOFDO_GCOV_EXPORT_H
+#define GCC_AUTOFDO_GCOV_EXPORT_H
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace gcov {
+class gcov_profile;
+}
+
+namespace autofdo {
+
+struct function_profile;
+
+using gcov_export_functions
+ = std::vector<std::pair<std::string, function_profile *>>;
+
+class gcov_profile_exporter
+{
+public:
+ static void export_profile (const gcov_export_functions &functions,
+ gcov::gcov_profile &out);
+};
+
+} /* namespace autofdo. */
+
+#endif /* GCC_AUTOFDO_GCOV_EXPORT_H. */
--
2.34.1