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