Implement parsing of GCOV AutoFDO v3 profiles into the shared profile
model, and deterministic serialization back out of it. The reader
validates profile structure and record bounds before exposing data to
higher-level tools; the writer emits the same format so dump, merge, and
create can round-trip v3 profiles.
afdo_tools/ChangeLog:
* src/gcov_reader.cc: New file.
* src/gcov_reader.h: New file.
* src/gcov_reader_v3.cc: New file.
* src/gcov_writer.cc: New file.
* src/gcov_writer.h: New file.
* src/gcov_writer_v3.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/gcov_reader.cc | 64 +++++
afdo_tools/src/gcov_reader.h | 84 ++++++
afdo_tools/src/gcov_reader_v3.cc | 451 +++++++++++++++++++++++++++++++
afdo_tools/src/gcov_writer.cc | 69 +++++
afdo_tools/src/gcov_writer.h | 80 ++++++
afdo_tools/src/gcov_writer_v3.cc | 262 ++++++++++++++++++
6 files changed, 1010 insertions(+)
create mode 100644 afdo_tools/src/gcov_reader.cc
create mode 100644 afdo_tools/src/gcov_reader.h
create mode 100644 afdo_tools/src/gcov_reader_v3.cc
create mode 100644 afdo_tools/src/gcov_writer.cc
create mode 100644 afdo_tools/src/gcov_writer.h
create mode 100644 afdo_tools/src/gcov_writer_v3.cc
diff --git a/afdo_tools/src/gcov_reader.cc b/afdo_tools/src/gcov_reader.cc
new file mode 100644
index 00000000000..21188bd8ccf
--- /dev/null
+++ b/afdo_tools/src/gcov_reader.cc
@@ -0,0 +1,64 @@
+/* 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_reader.h"
+#include "gcov.h"
+#include "gcov_io.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <string>
+
+namespace gcov {
+
+gcov_reader_base::gcov_reader_base (const std::string &path_)
+ : path (path_)
+{
+}
+
+gcov_reader_base::~gcov_reader_base ()
+{
+}
+
+/* Sniff a profile's format from its leading bytes (shared by afdo_dump /
+ afdo_merge to pick a reader). */
+profile_format
+detect_format (const std::string &path)
+{
+ /* Only the leading v3 magic word is needed; do not map the whole file. */
+ uint32_t magic = 0;
+ FILE *f = fopen (path.c_str (), "rb");
+ if (f == nullptr)
+ return profile_format::unknown;
+ const bool have_magic
+ = fread (&magic, 1, sizeof (magic), f) == sizeof (magic);
+ fclose (f);
+ if (have_magic && magic == v3::format::AFDO_GCOV_DATA_MAGIC)
+ return profile_format::v3_binary;
+ return profile_format::unknown;
+}
+
+std::unique_ptr<gcov_reader_base>
+make_gcov_reader (const std::string &path)
+{
+ if (detect_format (path) == profile_format::v3_binary)
+ return std::make_unique<gcov_reader_v3> (path);
+ return nullptr;
+}
+
+} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_reader.h b/afdo_tools/src/gcov_reader.h
new file mode 100644
index 00000000000..9cb97b14edd
--- /dev/null
+++ b/afdo_tools/src/gcov_reader.h
@@ -0,0 +1,84 @@
+/* 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_GCOV_READER_H
+#define GCC_GCOV_READER_H
+
+#include "gcov.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace gcov {
+
+class gcov_reader_base
+{
+protected:
+ gcov_profile profile;
+ std::string path;
+
+ uint32_t magic = 0;
+ uint32_t version = 0;
+
+public:
+ gcov_reader_base (const std::string &path_);
+ virtual ~gcov_reader_base ();
+ virtual bool read () = 0;
+ const gcov_profile *get_profile () const { return &profile; }
+
+ uint32_t get_version () const { return version; }
+};
+
+/* Reader for version 3 of the GCOV format. */
+class gcov_reader_v3 : public gcov_reader_base
+{
+ const uint8_t *cur_ = nullptr;
+ const uint8_t *end_ = nullptr;
+ bool read_summary (gcov_summary &summary);
+ bool read_file_names (std::vector<std::string> &file_names);
+ bool read_function_names (std::vector<std::string> &function_names,
+ std::vector<uint32_t> &file_name_map);
+ bool read_pos_count (std::unique_ptr<gcov_location> &loc);
+ bool read_inline_callsite (gcov_location_inline &callsite, unsigned depth);
+ bool read_function (gcov_function &func);
+ bool read_functions (std::vector<gcov_function> &functions);
+
+public:
+ explicit gcov_reader_v3 (const std::string &path_) : gcov_reader_base (path_)
+ {}
+ bool read () override;
+};
+
+enum class profile_format
+{
+ unknown,
+ v3_binary,
+};
+
+/* Sniff a profile file's format from its leading bytes. */
+profile_format detect_format (const std::string &path);
+
+/* Construct the reader appropriate for PATH, or return nullptr when its format
+ is unsupported by this build. */
+std::unique_ptr<gcov_reader_base> make_gcov_reader (const std::string &path);
+
+} /* namespace gcov. */
+
+#endif /* GCC_GCOV_READER_H. */
diff --git a/afdo_tools/src/gcov_reader_v3.cc b/afdo_tools/src/gcov_reader_v3.cc
new file mode 100644
index 00000000000..8e3cc674f5f
--- /dev/null
+++ b/afdo_tools/src/gcov_reader_v3.cc
@@ -0,0 +1,451 @@
+/* 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_reader.h"
+#include "gcov.h"
+#include "gcov_io.h"
+#include "file_mmap.h"
+
+#ifdef IN_GCC
+#include "intl.h"
+#endif
+
+#include <algorithm>
+#include <cstdint>
+#include <cstdio>
+#include <memory>
+
+namespace gcov {
+
+/* On-disk size of one summary histogram row: cutoff, min_count,
+ num_counts. */
+constexpr size_t DETAILED_ENTRY_BYTES
+ = sizeof (uint32_t) + 2 * sizeof (uint64_t);
+
+/* Hard cap on nested inline callsites. Remaining-byte checks alone still
+ allow a deep spine of tiny records to overflow the C stack. */
+constexpr unsigned MAX_INLINE_DEPTH = 64;
+
+/* Read a record length word and reject a record that cannot fit in the input.
+ The value is only a lower bound on the record: producers disagree on both
+ its unit and its coverage, as create_gcov counts 32-bit words and leaves
+ the file-name table out of the FILE_NAMES length and the v3 timestamps out
+ of the FUNCTION length. Comparing it against the bytes left accepts either
+ unit while still rejecting a record that claims more than the file holds;
+ parsing itself stays bounded by END. */
+static bool
+read_record_length (const uint8_t *&cur, const uint8_t *end)
+{
+ uint32_t record_length = 0;
+ if (!gcov_read_uint32 (cur, end, record_length))
+ return false;
+ return cur <= end && record_length <= (size_t) (end - cur);
+}
+
+/* Read a record tag and check it against EXPECTED. NAME identifies the
+ record in the diagnostic emitted on a mismatch. */
+static bool
+read_expected_tag (const uint8_t *&cur, const uint8_t *end, uint32_t expected,
+ const char *name)
+{
+ uint32_t tag;
+ if (!gcov_read_uint32 (cur, end, tag))
+ return false;
+ if (tag != expected)
+ {
+ fprintf (stderr,
+ _("Error: Expected %s tag (0x%x), got 0x%x\n"),
+ name,
+ (unsigned) expected,
+ (unsigned) tag);
+ return false;
+ }
+ return true;
+}
+
+/* Read the AFDO summary record into the profile's summary. */
+bool
+gcov_reader_v3::read_summary (gcov_summary &summary)
+{
+ if (!read_expected_tag
+ (cur_, end_, v3::format::GCOV_TAG_AFDO_SUMMARY, "AFDO_SUMMARY"))
+ return false;
+ if (!gcov_read_uint64 (cur_, end_, summary.total_count)
+ || !gcov_read_uint64 (cur_, end_, summary.max_count)
+ || !gcov_read_uint64 (cur_, end_, summary.max_function_count)
+ || !gcov_read_uint64 (cur_, end_, summary.num_counts)
+ || !gcov_read_uint64 (cur_, end_, summary.num_functions)
+ || !gcov_read_uint64 (cur_, end_, summary.num_detailed_entries))
+ return false;
+ /* Reject a count the rest of the record cannot hold; this also keeps the
+ value within size_t. */
+ const size_t remaining = static_cast<size_t> (end_ - cur_);
+ if (summary.num_detailed_entries > remaining / DETAILED_ENTRY_BYTES)
+ return false;
+ summary.detailed_entries.resize (
+ static_cast<size_t> (summary.num_detailed_entries));
+ for (auto &entry : summary.detailed_entries)
+ {
+ if (!gcov_read_uint32 (cur_, end_, entry.cutoff)
+ || !gcov_read_uint64 (cur_, end_, entry.min_count)
+ || !gcov_read_uint64 (cur_, end_, entry.num_counts))
+ return false;
+ }
+ return true;
+}
+
+/* Read the file-name table into the profile. */
+bool
+gcov_reader_v3::read_file_names (std::vector<std::string> &file_names)
+{
+ if (!read_expected_tag
+ (cur_, end_, v3::format::GCOV_TAG_AFDO_FILE_NAMES, "AFDO_FILE_NAMES"))
+ return false;
+
+ if (!read_record_length (cur_, end_))
+ return false;
+
+ uint32_t num_files = 0;
+ if (!gcov_read_uint32 (cur_, end_, num_files))
+ return false;
+ /* Length word plus trailing NUL. */
+ if (cur_ > end_
+ || (num_files != 0
+ && num_files > (size_t) (end_ - cur_) / (sizeof (uint32_t) + 1)))
+ return false;
+ file_names.reserve (num_files);
+ for (uint32_t i = 0; i < num_files; i++)
+ {
+ file_names.emplace_back ();
+ if (!gcov_read_string (cur_, end_, file_names.back ()))
+ return false;
+ }
+ return true;
+}
+
+/* Read the untagged function-name pool and its per-name file indices. */
+bool
+gcov_reader_v3::read_function_names (std::vector<std::string> &function_names,
+ std::vector<uint32_t> &file_name_map)
+{
+ uint32_t num_functions = 0;
+ if (!gcov_read_uint32 (cur_, end_, num_functions))
+ return false;
+ /* Name string (length + NUL) plus file-name index. */
+ if (cur_ > end_
+ || (num_functions != 0
+ && num_functions
+ > (size_t) (end_ - cur_)
+ / (sizeof (uint32_t) + 1 + sizeof (uint32_t))))
+ return false;
+ function_names.reserve (num_functions);
+ file_name_map.reserve (num_functions);
+ for (uint32_t i = 0; i < num_functions; i++)
+ {
+ function_names.emplace_back ();
+ if (!gcov_read_string (cur_, end_, function_names.back ()))
+ return false;
+ file_name_map.emplace_back ();
+ if (!gcov_read_uint32 (cur_, end_, file_name_map.back ()))
+ return false;
+ }
+ return true;
+}
+
+/* Read one position count into LOC: a plain count, or a call with its
+ indirect-call targets when the record carries any. */
+bool
+gcov_reader_v3::read_pos_count (std::unique_ptr<gcov_location> &loc)
+{
+ uint32_t encoded_offset = 0, num_targets = 0;
+ uint64_t count = 0;
+ if (!gcov_read_uint32 (cur_, end_, encoded_offset)
+ || !gcov_read_uint32 (cur_, end_, num_targets)
+ || !gcov_read_uint64 (cur_, end_, count))
+ return false;
+
+ std::unique_ptr<gcov_location> location;
+ if (num_targets == 0)
+ location = std::make_unique<gcov_location_normal> (count);
+ else
+ {
+ auto call = std::make_unique<gcov_location_call> (count);
+ /* value_profile_type, function_id, target_count. */
+ if (cur_ > end_
+ || (num_targets != 0
+ && num_targets
+ > (size_t) (end_ - cur_)
+ / (sizeof (uint32_t) + 2 * sizeof (uint64_t))))
+ return false;
+ call->callee_map.reserve (num_targets);
+ for (uint32_t target = 0; target < num_targets; target++)
+ {
+ uint32_t value_profile_type = 0;
+ uint64_t function_id = 0, target_count = 0;
+ if (!gcov_read_uint32 (cur_, end_, value_profile_type)
+ || !gcov_read_uint64 (cur_, end_, function_id)
+ || !gcov_read_uint64 (cur_, end_, target_count))
+ return false;
+ call->callee_map[function_id] = target_count;
+ }
+ location = std::move (call);
+ }
+
+ location->offset = encoded_offset >> 16;
+ location->discriminator = encoded_offset & 0xffff;
+ loc = std::move (location);
+ return true;
+}
+
+/* Read an inline callsite and, recursively, the whole inline subtree below
+ it into CALLSITE. DEPTH is the nesting level of this callsite. */
+bool
+gcov_reader_v3::read_inline_callsite (gcov_location_inline &callsite,
+ unsigned depth)
+{
+ if (depth >= MAX_INLINE_DEPTH)
+ return false;
+
+ uint32_t callsite_offset = 0, inline_func_name_idx = 0,
+ inline_num_pos_counts = 0, inline_num_callsites = 0;
+
+ if (!gcov_read_uint32 (cur_, end_, callsite_offset)
+ || !gcov_read_uint32 (cur_, end_, inline_func_name_idx)
+ || !gcov_read_uint32 (cur_, end_, inline_num_pos_counts)
+ || !gcov_read_uint32 (cur_, end_, inline_num_callsites))
+ return false;
+
+ if (inline_func_name_idx >= profile.get_function_names ().size ())
+ {
+ fprintf (stderr,
+ _("Error: v3 inline callsite name index out of range\n"));
+ return false;
+ }
+
+ callsite.offset = callsite_offset >> 16;
+ callsite.discriminator = callsite_offset & 0xffff;
+ callsite.inlined_sym.desc.name_index = inline_func_name_idx;
+ callsite.inlined_sym.id = inline_func_name_idx;
+
+ const size_t n_pos = inline_num_pos_counts;
+ const size_t n_cs = inline_num_callsites;
+ if (n_pos > ~(size_t) 0 - n_cs)
+ return false;
+ callsite.inlined_sym.locations.reserve (n_pos + n_cs);
+ for (uint32_t j = 0; j < inline_num_pos_counts; ++j)
+ {
+ std::unique_ptr<gcov_location> location = nullptr;
+ if (!read_pos_count (location))
+ return false;
+ callsite.inlined_sym.locations.emplace_back (std::move (location));
+ }
+
+ for (uint32_t j = 0; j < inline_num_callsites; ++j)
+ {
+ std::unique_ptr<gcov_location> nested
+ = std::make_unique<gcov_location_inline> ();
+ if (!read_inline_callsite (
+ *static_cast<gcov_location_inline *> (nested.get ()),
+ depth + 1))
+ return false;
+ callsite.inlined_sym.locations.emplace_back (std::move (nested));
+ }
+
+ callsite.inlined_sym.total_count
+ = gcov_function_subtree_count (callsite.inlined_sym);
+
+ return true;
+}
+
+/* Read one function record into FUNC, sorting its locations by offset and
+ discriminator. */
+bool
+gcov_reader_v3::read_function (gcov_function &func)
+{
+ if (!gcov_read_uint64 (cur_, end_, func.head_count)
+ || !gcov_read_uint64 (cur_, end_, func.timestamp))
+ return false;
+ uint32_t name_idx = 0, num_pos = 0, num_inline_calls = 0;
+ if (!gcov_read_uint32 (cur_, end_, name_idx))
+ return false;
+ const std::vector<std::string> &function_names
+ = profile.get_function_names ();
+ const std::vector<uint32_t> &file_name_map = profile.get_file_name_map ();
+ const std::vector<std::string> &file_names = profile.get_file_names ();
+ if (name_idx >= function_names.size () || name_idx >= file_name_map.size ())
+ {
+ fprintf (stderr, _("Error: v3 function name index out of range\n"));
+ return false;
+ }
+ func.desc.name_index = name_idx;
+ func.id = name_idx;
+ const uint32_t file_idx = file_name_map[name_idx];
+ func.desc.file_index
+ = (file_idx < file_names.size ()) ? file_idx : INVALID_INDEX;
+ if (!gcov_read_uint32 (cur_, end_, num_pos)
+ || !gcov_read_uint32 (cur_, end_, num_inline_calls))
+ return false;
+ const size_t n_pos = num_pos;
+ const size_t n_cs = num_inline_calls;
+ if (n_pos > ~(size_t) 0 - n_cs)
+ return false;
+ func.locations.reserve (n_pos + n_cs);
+ for (uint32_t i = 0; i < num_pos; i++)
+ {
+ std::unique_ptr<gcov_location> location = nullptr;
+ if (!read_pos_count (location))
+ return false;
+ func.locations.emplace_back (std::move (location));
+ }
+
+ for (uint32_t i = 0; i < num_inline_calls; i++)
+ {
+ std::unique_ptr<gcov_location> location
+ = std::make_unique<gcov_location_inline> ();
+ if (!read_inline_callsite (
+ *static_cast<gcov_location_inline *> (location.get ()), 0))
+ return false;
+ func.locations.emplace_back (std::move (location));
+ }
+
+ std::stable_sort (func.locations.begin (),
+ func.locations.end (),
+ [] (const std::unique_ptr<gcov_location> &one,
+ const std::unique_ptr<gcov_location> &two)
+ {
+ return one->offset < two->offset
+ || (one->offset == two->offset
+ && one->discriminator < two->discriminator);
+ });
+
+ func.total_count = gcov_function_subtree_count (func);
+ return true;
+}
+
+/* Read the function records and index them by id. */
+bool
+gcov_reader_v3::read_functions (std::vector<gcov_function> &functions)
+{
+ if (!read_expected_tag
+ (cur_, end_, v3::format::GCOV_TAG_AFDO_FUNCTION, "AFDO_FUNCTION"))
+ return false;
+
+ if (!read_record_length (cur_, end_))
+ return false;
+
+ uint32_t num_functions = 0;
+ if (!gcov_read_uint32 (cur_, end_, num_functions))
+ return false;
+ /* head_count, timestamp, name index, num_pos, num_inline_calls. */
+ if (cur_ > end_
+ || (num_functions != 0
+ && num_functions
+ > (size_t) (end_ - cur_)
+ / (2 * sizeof (uint64_t) + 3 * sizeof (uint32_t))))
+ return false;
+
+ functions.reserve (num_functions);
+ for (uint32_t i = 0; i < num_functions; i++)
+ {
+ functions.emplace_back ();
+ if (!read_function (functions.back ()))
+ return false;
+ }
+
+ return true;
+}
+
+/* Map the profile and read its header and the four AFDO records. */
+bool
+gcov_reader_v3::read ()
+{
+ afdo_tools::file_mmap fb;
+ if (!fb.map (path))
+ {
+ fprintf (stderr, _("Error: cannot mmap %s\n"), path.c_str ());
+ return false;
+ }
+ cur_ = fb.data ();
+ end_ = fb.data () + fb.size ();
+
+ uint32_t magic = 0;
+ if (!gcov_read_uint32 (cur_, end_, magic))
+ {
+ fprintf (stderr, _("Error: Failed to read magic number\n"));
+ return false;
+ }
+
+ if (magic != v3::format::AFDO_GCOV_DATA_MAGIC)
+ {
+ fprintf (stderr, _("Error: Invalid magic number\n"));
+ return false;
+ }
+
+ uint32_t checksum = 0;
+ if (!gcov_read_uint32 (cur_, end_, version)
+ || !gcov_read_uint32 (cur_, end_, checksum))
+ {
+ fprintf (stderr, _("Error: Failed to read version/checksum\n"));
+ return false;
+ }
+ (void) checksum;
+
+ if (version != v3::format::AFDO_GCOV_VERSION)
+ {
+ fprintf (stderr, _("Error: unsupported v3 version %u\n"),
+ (unsigned) version);
+ return false;
+ }
+
+ gcov_summary summary;
+ if (!read_summary (summary))
+ {
+ fprintf (stderr, _("Error: Unable to read summary information\n"));
+ return false;
+ }
+ profile.set_summary (std::move (summary));
+
+ std::vector<std::string> file_names;
+ if (!read_file_names (file_names))
+ {
+ fprintf (stderr, _("Error: Unable to read file names\n"));
+ return false;
+ }
+
+ std::vector<std::string> function_names;
+ std::vector<uint32_t> file_name_map;
+ if (!read_function_names (function_names, file_name_map))
+ {
+ fprintf (stderr, _("Error: Unable to read function names\n"));
+ return false;
+ }
+ profile.set_names (std::move (file_names), std::move (function_names),
+ std::move (file_name_map));
+
+ std::vector<gcov_function> functions;
+ if (!read_functions (functions))
+ {
+ fprintf (stderr, _("Error: Unable to read function information\n"));
+ return false;
+ }
+ profile.set_functions (std::move (functions));
+
+ return true;
+}
+
+} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_writer.cc b/afdo_tools/src/gcov_writer.cc
new file mode 100644
index 00000000000..70660e505a3
--- /dev/null
+++ b/afdo_tools/src/gcov_writer.cc
@@ -0,0 +1,69 @@
+/* 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_writer.h"
+
+#include <cstdio>
+#include <string>
+
+namespace gcov {
+
+/* Open PATH for writing. A failure is reported here and left for write () to
+ turn into an error, so construction never throws. */
+gcov_writer_base::gcov_writer_base (const std::string &path,
+ const gcov_profile *profile_)
+ : profile (profile_)
+{
+ file = fopen (path.c_str (), "wb");
+ if (!file)
+ fprintf (stderr, _("Error: Cannot open file %s\n"), path.c_str ());
+}
+
+gcov_writer_base::~gcov_writer_base ()
+{
+ if (file)
+ fclose (file);
+}
+
+bool
+gcov_writer_base::close ()
+{
+ if (!file)
+ return false;
+
+ const bool had_error = ferror (file) != 0;
+ const int rc = fclose (file);
+ file = nullptr;
+ if (had_error || rc != 0)
+ {
+ fprintf (stderr, _("Error: failed to close profile output file\n"));
+ return false;
+ }
+ return true;
+}
+
+std::unique_ptr<gcov_writer_base>
+make_gcov_writer (const std::string &path, const gcov_profile *profile,
+ const gcov_writer_options &options)
+{
+ if (options.version == 3)
+ return std::make_unique<gcov_writer_v3> (path, profile);
+ return nullptr;
+}
+
+} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_writer.h b/afdo_tools/src/gcov_writer.h
new file mode 100644
index 00000000000..f6a5675cc13
--- /dev/null
+++ b/afdo_tools/src/gcov_writer.h
@@ -0,0 +1,80 @@
+/* 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_GCOV_WRITER_H
+#define GCC_GCOV_WRITER_H
+
+#include "gcov.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace gcov {
+
+class gcov_writer_base
+{
+protected:
+ const gcov_profile *profile = nullptr;
+ FILE *file = nullptr;
+
+ /* Flush and close FILE. Returns true only if the stream had no prior
+ error and fclose succeeded. */
+ bool close ();
+
+public:
+ gcov_writer_base (const std::string &path, const gcov_profile *profile_);
+ virtual ~gcov_writer_base ();
+
+ virtual bool write () = 0;
+};
+
+struct gcov_writer_options
+{
+ int version = 3;
+};
+
+class gcov_writer_v3 : public gcov_writer_base
+{
+ void write_summary ();
+ bool write_file_names ();
+ void write_function_names ();
+ void write_pos_count (const gcov_location *loc);
+ void write_locations (const gcov_function &func);
+ void write_inline_callsite (const gcov_location_inline &callsite);
+ void write_function (const gcov_function &func);
+ bool write_functions ();
+
+public:
+ gcov_writer_v3 (const std::string &path, const gcov_profile *profile_)
+ : gcov_writer_base (path, profile_)
+ {}
+ bool write () override;
+};
+
+/* Construct a writer for OPTIONS, or return nullptr when the requested format
+ is unsupported by this build. */
+std::unique_ptr<gcov_writer_base>
+make_gcov_writer (const std::string &path, const gcov_profile *profile,
+ const gcov_writer_options &options);
+
+} /* namespace gcov. */
+
+#endif /* GCC_GCOV_WRITER_H. */
diff --git a/afdo_tools/src/gcov_writer_v3.cc b/afdo_tools/src/gcov_writer_v3.cc
new file mode 100644
index 00000000000..bb34432c4a9
--- /dev/null
+++ b/afdo_tools/src/gcov_writer_v3.cc
@@ -0,0 +1,262 @@
+/* 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_writer.h"
+#include "gcov.h"
+#include "gcov_io.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <vector>
+
+namespace gcov {
+namespace {
+
+/* v3 packs a location into one word as (line offset << 16) | discriminator.
*/
+uint32_t
+encode_location (const gcov_location &loc)
+{
+ return (loc.offset << 16) | (loc.discriminator & 0xffffu);
+}
+
+/* Patch the record length word at LENGTH_POS with the size of the payload
+ written after it, counted in 32-bit words as v3 readers expect. */
+bool
+finish_record_length (FILE *file, long length_pos)
+{
+ const long end = ftell (file);
+ if (length_pos < 0 || end < 0)
+ return false;
+ const long body = length_pos + (long) sizeof (uint32_t);
+ if (end < body)
+ return false;
+ /* Strings are not padded, so round the payload up to a whole word. */
+ const unsigned long words
+ = ((unsigned long) (end - body) + sizeof (uint32_t) - 1)
+ / sizeof (uint32_t);
+ if (words > 0xfffffffful)
+ return false;
+ if (fseek (file, length_pos, SEEK_SET) != 0)
+ return false;
+ gcov_write_uint32 (file, (uint32_t) words);
+ return fseek (file, end, SEEK_SET) == 0;
+}
+
+} /* Anonymous namespace. */
+
+/* SUMMARY record: tag + payload, no length word. */
+void
+gcov_writer_v3::write_summary ()
+{
+ const gcov_summary &s = profile->get_summary ();
+ gcov_write_uint32 (file, v3::format::GCOV_TAG_AFDO_SUMMARY);
+ gcov_write_uint64 (file, s.total_count);
+ gcov_write_uint64 (file, s.max_count);
+ gcov_write_uint64 (file, s.max_function_count);
+ gcov_write_uint64 (file, s.num_counts);
+ gcov_write_uint64 (file, s.num_functions);
+ gcov_write_uint64 (file, s.num_detailed_entries);
+ for (const auto &e : s.detailed_entries)
+ {
+ gcov_write_uint32 (file, e.cutoff);
+ gcov_write_uint64 (file, e.min_count);
+ gcov_write_uint64 (file, e.num_counts);
+ }
+}
+
+/* num_functions, then (name, file_index) per entry with no tag. Emitted
inside
+ the FILE_NAMES record. */
+void
+gcov_writer_v3::write_function_names ()
+{
+ const std::vector<std::string> &function_names
+ = profile->get_function_names ();
+ const std::vector<uint32_t> &file_name_map = profile->get_file_name_map ();
+ gcov_write_uint32 (file, static_cast<uint32_t> (function_names.size ()));
+ for (size_t i = 0; i < function_names.size (); ++i)
+ {
+ gcov_write_string (file, function_names[i]);
+ const uint32_t file_idx
+ = (i < file_name_map.size ()) ? file_name_map[i] : INVALID_INDEX;
+ gcov_write_uint32 (file, file_idx);
+ }
+}
+
+/* FILE_NAMES record: tag + length word + file table + function-name table. */
+bool
+gcov_writer_v3::write_file_names ()
+{
+ gcov_write_uint32 (file, v3::format::GCOV_TAG_AFDO_FILE_NAMES);
+ const long length_pos = ftell (file);
+ gcov_write_uint32 (file, 0);
+
+ const std::vector<std::string> &file_names = profile->get_file_names ();
+ gcov_write_uint32 (file, static_cast<uint32_t> (file_names.size ()));
+ for (const auto &path : file_names)
+ gcov_write_string (file, path);
+
+ write_function_names ();
+ return finish_record_length (file, length_pos);
+}
+
+/* One non-inline position: the packed location word, then either the sorted
+ indirect-call target histogram or a plain count. */
+void
+gcov_writer_v3::write_pos_count (const gcov_location *loc)
+{
+ gcov_write_uint32 (file, encode_location (*loc));
+
+ if (loc->type == gcov_location::kind::call)
+ {
+ const gcov_location_call *call
+ = static_cast<const gcov_location_call *> (loc);
+ /* Canonical order is targets sorted by function id. */
+ std::vector<std::pair<uint64_t, uint64_t>> targets (
+ call->callee_map.begin (), call->callee_map.end ());
+ std::sort (targets.begin (),
+ targets.end (),
+ [] (const std::pair<uint64_t, uint64_t> &a,
+ const std::pair<uint64_t, uint64_t> &b)
+ {
+ return a.first < b.first;
+ });
+ gcov_write_uint32 (file, static_cast<uint32_t> (targets.size ()));
+ gcov_write_uint64 (file, call->count);
+ for (const auto &t : targets)
+ {
+ gcov_write_uint32 (file, v3::HIST_TYPE_INDIR_CALL_TOPN);
+ gcov_write_uint64 (file, t.first); /* Function id (name index). */
+ gcov_write_uint64 (file, t.second); /* Target count. */
+ }
+ }
+ else
+ {
+ const gcov_location_normal *normal
+ = static_cast<const gcov_location_normal *> (loc);
+ gcov_write_uint32 (file, 0); /* num_targets. */
+ gcov_write_uint64 (file, normal->count);
+ }
+}
+
+/* Body shared by top-level and inlined functions: FUNC's locations split into
+ pos counts (normal/call) and inline callsites, both counts, then the two
+ runs. */
+void
+gcov_writer_v3::write_locations (const gcov_function &func)
+{
+ std::vector<const gcov_location *> pos;
+ std::vector<const gcov_location_inline *> inl;
+ pos.reserve (func.locations.size ());
+ inl.reserve (func.locations.size ());
+ for (const auto &loc : func.locations)
+ {
+ if (loc->type == gcov_location::kind::inlined)
+ inl.push_back (static_cast<const gcov_location_inline *> (loc.get ()));
+ else
+ pos.push_back (loc.get ());
+ }
+
+ gcov_write_uint32 (file, static_cast<uint32_t> (pos.size ()));
+ gcov_write_uint32 (file, static_cast<uint32_t> (inl.size ()));
+ for (const gcov_location *loc : pos)
+ write_pos_count (loc);
+ for (const gcov_location_inline *cs : inl)
+ write_inline_callsite (*cs);
+}
+
+/* One inline callsite: packed location word, callee id, then the callee's
+ body. */
+void
+gcov_writer_v3::write_inline_callsite (const gcov_location_inline &callsite)
+{
+ gcov_write_uint32 (file, encode_location (callsite));
+ gcov_write_uint32 (file, callsite.inlined_sym.id);
+ write_locations (callsite.inlined_sym);
+}
+
+/* One top-level function record: head count, timestamp, id, then its body. */
+void
+gcov_writer_v3::write_function (const gcov_function &func)
+{
+ gcov_write_uint64 (file, func.head_count);
+ gcov_write_uint64 (file, func.timestamp);
+ gcov_write_uint32 (file, func.id);
+ write_locations (func);
+}
+
+/* FUNCTION record: tag + length word + num_functions + records. */
+bool
+gcov_writer_v3::write_functions ()
+{
+ gcov_write_uint32 (file, v3::format::GCOV_TAG_AFDO_FUNCTION);
+ const long length_pos = ftell (file);
+ gcov_write_uint32 (file, 0);
+
+ const std::vector<gcov_function> &functions = profile->get_functions ();
+ gcov_write_uint32 (file, static_cast<uint32_t> (functions.size ()));
+ for (const auto &func : functions)
+ write_function (func);
+ return finish_record_length (file, length_pos);
+}
+
+/* Write the complete v3 profile: file header, summary, name tables, function
+ records and the trailing module-grouping / working-set stubs. */
+bool
+gcov_writer_v3::write ()
+{
+ if (!file)
+ {
+ fprintf (stderr, _("Error: failed to create output file\n"));
+ return false;
+ }
+ if (profile == nullptr)
+ {
+ fprintf (stderr, _("Error: no profile data to write\n"));
+ return false;
+ }
+
+ gcov_write_uint32 (file, v3::format::AFDO_GCOV_DATA_MAGIC);
+ gcov_write_uint32 (file, v3::format::AFDO_GCOV_VERSION);
+ gcov_write_uint32 (file, 0); /* Checksum (not used). */
+
+ write_summary ();
+ if (!write_file_names () || !write_functions ())
+ {
+ fprintf (stderr, _("Error: failed to write GCOV record length\n"));
+ return false;
+ }
+
+ /* MODULE_GROUPING. */
+ gcov_write_uint32 (file, v3::format::GCOV_TAG_MODULE_GROUPING);
+ gcov_write_uint32 (file, 1); /* Payload length in words. */
+ gcov_write_uint32 (file, 0); /* num_modules = 0. */
+
+ /* WORKING_SET: GCC's auto-profile reader does not consume this, but emit a
+ zero-filled record so the v3 file stays structurally complete. */
+ gcov_write_uint32 (file, v3::format::GCOV_TAG_AFDO_WORKING_SET);
+ gcov_write_uint32 (file, v3::format::GCOV_WORKING_SET_BUCKETS * 3);
+ for (uint32_t i = 0; i < v3::format::GCOV_WORKING_SET_BUCKETS; ++i)
+ {
+ gcov_write_uint32 (file, 0); /* num_counters. */
+ gcov_write_uint64 (file, 0); /* min_counter. */
+ }
+
+ return close ();
+}
+
+} /* namespace gcov. */
--
2.34.1