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

Reply via email to