Add the afdo_dump command-line frontend for inspecting AutoFDO profiles in a
stable textual form.  The patch is intentionally limited to a consumer of
the previously introduced reader API.

afdo_tools/ChangeLog:

        * src/afdo_dump.cc: 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/afdo_dump.cc | 267 ++++++++++++++++++++++++++++++++++++
 1 file changed, 267 insertions(+)
 create mode 100644 afdo_tools/src/afdo_dump.cc

diff --git a/afdo_tools/src/afdo_dump.cc b/afdo_tools/src/afdo_dump.cc
new file mode 100644
index 00000000000..4d30bb0dde7
--- /dev/null
+++ b/afdo_tools/src/afdo_dump.cc
@@ -0,0 +1,267 @@
+/* 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 "gcov.h"
+#include "gcov_reader.h"
+#include "version.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdint>
+#include <cstdio>
+#include <getopt.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+/* Dump an AutoFDO profile in the standard dump_gcov text layout.  */
+
+namespace {
+
+void
+print_usage (const char *prog, FILE *stream)
+{
+  fprintf (stream,
+          _("Usage: %s [OPTIONS] <profile.gcov>\n"
+            "\n"
+            "Dump an AutoFDO profile in text form.\n"
+            "\n"
+            "  -h, --help           Print this help, then exit\n"
+            "  -v, --version        Print version information, then exit\n"
+            "\nFor bug reporting instructions, please see:\n"
+            "%s.\n"),
+          prog, bug_report_url);
+}
+
+/* Returned by name/file lookups when the id is out of range.  */
+const std::string empty_string;
+
+/* Source file declaring the function with name id ID, via P's per-function
+   file index.  */
+const std::string &
+file_for_id (const gcov::gcov_profile &p, uint32_t id)
+{
+  const auto &map = p.get_file_name_map ();
+  if (id >= map.size ())
+    return empty_string;
+  const uint32_t fidx = map[id];
+  const auto &files = p.get_file_names ();
+  if (fidx >= files.size ())
+    return empty_string;
+  return files[fidx];
+}
+
+const std::string &
+name_for_id (const gcov::gcov_profile &p, uint32_t id)
+{
+  const auto &names = p.get_function_names ();
+  return id < names.size () ? names[id] : empty_string;
+}
+
+void
+print_location_prefix (uint32_t rel_line, uint32_t discriminator)
+{
+  if (discriminator > 0)
+    printf ("%u.%u: ", rel_line, discriminator);
+  else
+    printf ("%u: ", rel_line);
+}
+
+/* Indirect-call suffix uses the execution site's file, falling back to the
+   callee's declaration file (or the sole TU file).  */
+void
+print_indirect_targets (const gcov::gcov_location_call &call,
+                       const gcov::gcov_profile &p,
+                       const std::string &caller_file)
+{
+  std::vector<std::pair<uint64_t, uint64_t>> targets (
+    call.callee_map.begin (), call.callee_map.end ());
+  std::sort (
+    targets.begin (), targets.end (), [] (const auto &a, const auto &b)
+    {
+      return a.second > b.second || (a.second == b.second && a.first > 
b.first);
+    });
+  for (const auto &target : targets)
+    {
+      const uint64_t name_id = target.first;
+      const uint64_t tgt_count = target.second;
+      std::string file = caller_file;
+      if (file.empty ())
+       file = file_for_id (p, static_cast<uint32_t> (name_id));
+      if (file.empty () && p.get_file_names ().size () == 1)
+       file = p.get_file_names ()[0];
+      printf ("  %s:",
+             name_for_id (p, static_cast<uint32_t> (name_id)).c_str ());
+      if (!file.empty ())
+       printf ("%s:", file.c_str ());
+      printf ("%" PRIu64, tgt_count);
+    }
+}
+
+bool
+location_less (const gcov::gcov_location *a, const gcov::gcov_location *b)
+{
+  return a->offset < b->offset
+        || (a->offset == b->offset && a->discriminator < b->discriminator);
+}
+
+/* Print the position counts of FN, then recurse into its inline callsites.  */
+void
+dump_body (const gcov::gcov_function &fn, const gcov::gcov_profile &p,
+          int indent)
+{
+  const std::string body_indent (indent + 2, ' ');
+  const std::string caller_file = file_for_id (p, fn.id);
+
+  std::vector<const gcov::gcov_location *> positions;
+  std::vector<const gcov::gcov_location_inline *> inlines;
+  positions.reserve (fn.locations.size ());
+  inlines.reserve (fn.locations.size ());
+  for (const auto &loc : fn.locations)
+    {
+      if (loc->type == gcov::gcov_location::kind::inlined)
+       inlines.push_back (
+         static_cast<const gcov::gcov_location_inline *> (loc.get ()));
+      else
+       positions.push_back (loc.get ());
+    }
+  std::sort (positions.begin (), positions.end (), location_less);
+  std::sort (
+    inlines.begin (),
+    inlines.end (),
+    [] (const gcov::gcov_location_inline *a,
+       const gcov::gcov_location_inline *b) { return location_less (a, b); });
+
+  /* Pass 1: position counts, emitted before the child callsites.  */
+  for (const gcov::gcov_location *loc : positions)
+    {
+      printf ("%s", body_indent.c_str ());
+      print_location_prefix (loc->offset, loc->discriminator);
+      printf ("%" PRIu64, loc->total_count ());
+      if (loc->type == gcov::gcov_location::kind::call)
+       print_indirect_targets (
+         static_cast<const gcov::gcov_location_call &> (*loc), p, caller_file);
+      printf ("\n");
+    }
+
+  /* Pass 2: inline callsites (always after position counts).  */
+  for (const gcov::gcov_location_inline *inl : inlines)
+    {
+      const gcov::gcov_function &callee = inl->inlined_sym;
+      const std::string &callee_file = file_for_id (p, callee.id);
+      printf ("%s", body_indent.c_str ());
+      print_location_prefix (inl->offset, inl->discriminator);
+      printf ("%s:", name_for_id (p, callee.id).c_str ());
+      if (!callee_file.empty ())
+       printf ("%s", callee_file.c_str ());
+      printf (" total:%" PRIu64 "\n", callee.total_count);
+      dump_body (callee, p, indent + 2);
+    }
+}
+
+/* Print the header line of the top-level function FN, then its body.
+   validate_determinism.py scrapes the timestamp back out of this line.  */
+void
+dump_root (const gcov::gcov_function &fn, const gcov::gcov_profile &p)
+{
+  std::string path = file_for_id (p, fn.id);
+  if (path.empty () && p.get_file_names ().size () == 1)
+    path = p.get_file_names ()[0];
+  printf ("%s:%s total:%" PRIu64 " head:%" PRIu64,
+         name_for_id (p, fn.id).c_str (),
+         path.c_str (),
+         fn.total_count,
+         fn.head_count);
+  printf (" timestamp: %" PRIu64, fn.timestamp);
+  printf ("\n");
+  dump_body (fn, p, 0);
+}
+
+} /* anonymous namespace.  */
+
+int
+main (int argc, char *argv[])
+{
+  afdo_init_libintl ();
+
+  static option long_options[]
+    = {{"help", no_argument, nullptr, 'h'},
+       {"version", no_argument, nullptr, 'v'},
+       {nullptr, 0, nullptr, 0}};
+  int c;
+  while ((c = getopt_long_only (argc, argv, "hv", long_options, nullptr)) != 
-1)
+    {
+      if (c == 'h')
+       {
+         print_usage (argv[0], stdout);
+         return 0;
+       }
+      if (c == 'v')
+       {
+         printf ("afdo_dump %s%s\n", pkgversion_string, version_string);
+         printf (_("Copyright %s 2026 Free Software Foundation, Inc.\n"),
+                 _("(C)"));
+         printf (_("This is free software; see the source for copying "
+                   "conditions.  There is NO\n"
+                   "warranty; not even for MERCHANTABILITY or FITNESS FOR A "
+                   "PARTICULAR PURPOSE.\n"));
+         return 0;
+       }
+      return 1;
+    }
+
+  if (optind >= argc)
+    {
+      print_usage (argv[0], stderr);
+      return 1;
+    }
+  const char *path = argv[optind];
+
+  std::unique_ptr<gcov::gcov_reader_base> reader
+    = gcov::make_gcov_reader (path);
+  if (!reader)
+    {
+      fprintf (stderr, _("Error: unsupported profile format: %s\n"), path);
+      return 1;
+    }
+
+  if (!reader->read ())
+    {
+      fprintf (stderr, _("Error: Failed to read %s\n"), path);
+      return 1;
+    }
+  const gcov::gcov_profile &p = *reader->get_profile ();
+
+  /* Roots ordered by descending total, then name.  */
+  std::vector<size_t> root_order;
+  root_order.reserve (p.get_functions ().size ());
+  for (size_t i = 0; i < p.get_functions ().size (); ++i)
+    root_order.push_back (i);
+  std::sort (root_order.begin (), root_order.end (), [&] (size_t a, size_t b)
+  {
+    const gcov::gcov_function &fa = p.get_functions ()[a];
+    const gcov::gcov_function &fb = p.get_functions ()[b];
+    if (fa.total_count != fb.total_count)
+      return fa.total_count > fb.total_count;
+    return name_for_id (p, fa.id) < name_for_id (p, fb.id);
+  });
+  for (size_t root_idx : root_order)
+    dump_root (p.get_functions ()[root_idx], p);
+
+  return 0;
+}
-- 
2.34.1

Reply via email to