From: Dhruv Chawla <[email protected]>
Add section-based GCOV AutoFDO v4 profile I/O to afdo_tools:
- extend the shared profile model for v4 sections
- add gcov_reader_v4 / gcov_writer_v4 for binary, compact, and text
- teach the reader and writer factories to select v3 or v4
Existing afdo_dump, afdo_merge, and afdo_create consume v4 through the
factories without format-specific call sites. Compiler-side consumption
of these profiles is added in the next patch.
The changes to the version 4 format with this patch are:
(a) a self-describing, fixed-width section header table (the section type
and compact flag are recorded in each table entry).
(b) removal of the global compact flag from the file header (the byte
becomes reserved) - the table is always fixed-width and per-section
compactness lives in each section's bitmask.
(c) two new section types, SECTION_INLINE_INDICES (a per-file-group reverse
index of cross-module inline edges) and SECTION_INLINE_INDEX_TABLE (a
directory locating each group's index section).
The summary and file-name sections also become ordinary entries in the
unified section table (no longer dedicated header fields).
The textual format gains one change: indirect-call targets now carry the
callee's name, so a symbol that appears only as an indirect target survives
a text round-trip. It is otherwise unchanged. The change in the format is:
from callsites = { OFF[.DISC] -> { ID = COUNT, ... }, ... }
to callsites = { OFF[.DISC] -> { "NAME":ID = COUNT, ... }, ... }
The formats of the new sections in the binary format are:
SECTION_INLINE_INDEX_TABLE (0x06)
One section per profile: a directory mapping a file group id to the section
index of that group's SECTION_INLINE_INDICES. It lets a per-TU read find and
seek a single group's index section without reading every group's index.
INLINE_TABLE_NUM_ENTRIES int number of mapped groups
per entry:
FILE_ID int file group id
SECTION_INDEX int index of that group's INLINE_INDICES
SECTION_INLINE_INDICES (0x07)
A per-file-group reverse index of cross-module inline edges. One section is
emitted per file group (as defined by FILE_NAMES) that has cross-group
inliners: the set of top-level symbol ids (defined in *other* groups) whose
inline tree contains a symbol of this group. Edges within a single group
are omitted (the group's own SYMBOL_INFO already carries them).
INLINE_INDEX_NUM_INLINERS int count for this group
INLINER_SYMBOL_ID int (x N) top-level symbol id
In both section bodies "int" follows the section's compact flag (varint when
compact, big-endian otherwise), as for other section bodies.
Both sections are derived data: a writer regenerates them from the inline
tree, so a reader that does not need them simply skips the sections. They
are emitted by default in v4.
Use: to read translation unit T, a reader takes T's own functions
(FILE_NAMES group for T) plus, via the table and index, the top-level
functions in other groups that inline any of T's symbols (which is the set
the auto-profile offline pass needs to recover cross-module-inlined
profiles) and parses only their SYMBOL_INFO.
Regression tested on aarch64-linux-gnu with no new regressions.
afdo_tools/ChangeLog:
* Makefile.in: Build gcov_reader_v4 and gcov_writer_v4.
* src/afdo_create.cc (write_gcov_output): Select the output writer
through make_gcov_writer, so --gcov_version 4, --compact and
--format text emit v4 profiles.
* src/afdo_dump.cc (main): Accept --gcov_version and dump v4
profiles through the reader factory.
* src/afdo_merge.cc (main): Accept --gcov_version, --compact
and --format, and merge v3 and v4 inputs.
* src/gcov.h: Extend the profile model for GCOV v4 sections.
* src/gcov_io.cc: Add GCOV v4 I/O primitives.
* src/gcov_io.h: Likewise.
* src/gcov_merge.cc (gcov_merger): Merge v4 section data.
* src/gcov_merge.h: Likewise.
* src/gcov_reader.cc (make_gcov_reader): Detect and construct
gcov_reader_v4 for binary and text v4 profiles.
* src/gcov_reader.h: Declare gcov_reader_v4.
* src/gcov_reader_v4.cc: New file.
* src/gcov_writer.cc (make_gcov_writer): Construct
gcov_writer_v4 for version 4 output.
* src/gcov_writer.h: Declare gcov_writer_v4.
* src/gcov_writer_v4.cc: New file.
Signed-off-by: Dhruv Chawla <[email protected]>
Co-authored-by: Kugan Vivekanandarajah <[email protected]>
Co-authored-by: Rahul Shinde <[email protected]>
---
afdo_tools/Makefile.in | 2 +
afdo_tools/src/afdo_create.cc | 54 +-
afdo_tools/src/afdo_dump.cc | 28 +-
afdo_tools/src/afdo_merge.cc | 56 +-
afdo_tools/src/gcov.h | 83 ++-
afdo_tools/src/gcov_io.cc | 297 ++++++++++
afdo_tools/src/gcov_io.h | 46 ++
afdo_tools/src/gcov_merge.cc | 3 +
afdo_tools/src/gcov_merge.h | 19 +
afdo_tools/src/gcov_reader.cc | 61 +-
afdo_tools/src/gcov_reader.h | 87 ++-
afdo_tools/src/gcov_reader_v4.cc | 952 +++++++++++++++++++++++++++++++
afdo_tools/src/gcov_writer.cc | 10 +-
afdo_tools/src/gcov_writer.h | 120 ++++
afdo_tools/src/gcov_writer_v4.cc | 802 ++++++++++++++++++++++++++
15 files changed, 2564 insertions(+), 56 deletions(-)
create mode 100644 afdo_tools/src/gcov_reader_v4.cc
create mode 100644 afdo_tools/src/gcov_writer_v4.cc
diff --git a/afdo_tools/Makefile.in b/afdo_tools/Makefile.in
index 378cd470dc6..ce3284773c3 100644
--- a/afdo_tools/Makefile.in
+++ b/afdo_tools/Makefile.in
@@ -46,6 +46,7 @@ PROFILER_SRCS = src/dwarf_reader.cc \
src/autofdo_correlator.cc \
src/gcov_writer.cc \
src/gcov_writer_v3.cc \
+ src/gcov_writer_v4.cc \
src/gcov_io.cc \
src/gcov_summary.cc \
src/profile_timer.cc
@@ -54,6 +55,7 @@ PROFILER_OBJS = $(PROFILER_SRCS:.cc=.o)
GCOV_RDWR_SRCS = src/gcov_reader.cc src/gcov_writer.cc \
src/gcov_reader_v3.cc src/gcov_writer_v3.cc \
+ src/gcov_reader_v4.cc src/gcov_writer_v4.cc \
src/gcov_io.cc src/file_mmap.cc
diff --git a/afdo_tools/src/afdo_create.cc b/afdo_tools/src/afdo_create.cc
index 139531edb1c..eededd7468a 100644
--- a/afdo_tools/src/afdo_create.cc
+++ b/afdo_tools/src/afdo_create.cc
@@ -92,6 +92,7 @@ struct program_options
std::string output_file;
std::vector<std::string> command_argv;
autofdo::profiler_run_options run;
+ gcov::gcov_writer_options writer;
perf_data::sampling_mode sampling_mode
= perf_data::sampling_mode::BRANCH_STACK;
bool sample_period_set = false;
@@ -150,6 +151,10 @@ print_usage (const char *program_name)
" -v, --version Show version\n\n"
"create_gcov compatibility options:\n"
" --gcov FILE Output GCOV file\n"
+ " --gcov_version ARG Version of GCOV to emit (3 or 4)\n"
+ " --compact v4 only: varint-encode for smaller "
+ "output\n"
+ " --format ARG v4 only: 'binary' (default) or 'text'\n"
" --profile FILE Path to perf.data\n"
" --profiler ARG Profile type (Branch-stack ('perf') or "
"SPE ('perf_spe'))\n"
@@ -175,6 +180,9 @@ parse_arguments (int argc, char *argv[], program_options
&options)
{"no-branch-stack", no_argument, nullptr, 'L'},
{"no-lbr", no_argument, nullptr, 'L'},
{"gcov", required_argument, nullptr, 0},
+ {"gcov_version", required_argument, nullptr, 0},
+ {"compact", no_argument, nullptr, 0},
+ {"format", required_argument, nullptr, 0},
{"profile", required_argument, nullptr, 0},
{"profiler", required_argument, nullptr, 0},
{"spe", no_argument, nullptr, 's'},
@@ -188,7 +196,7 @@ parse_arguments (int argc, char *argv[], program_options
&options)
int c;
int option_index = 0;
- /* create_gcov and SPEC pass long options with one dash. */
+ /* getopt_long_only: create_gcov / SPEC pass -gcov_version (one dash). */
while ((c = getopt_long_only (
argc, argv, "p:b:o:m:D:LsF:j:hv", long_options, &option_index))
!= -1)
@@ -276,6 +284,15 @@ parse_arguments (int argc, char *argv[], program_options
&options)
}
else if (!strcmp (long_options[option_index].name, "gcov"))
options.output_file = optarg;
+ else if (!strcmp (long_options[option_index].name, "gcov_version"))
+ {
+ if (!gcov::parse_profile_version (optarg, options.writer.version))
+ {
+ fprintf (stderr, _("Unsupported GCOV version: %s\n"),
+ optarg);
+ return false;
+ }
+ }
else if (!strcmp (long_options[option_index].name, "sample-period"))
{
uint64_t n = 0;
@@ -289,6 +306,23 @@ parse_arguments (int argc, char *argv[], program_options
&options)
options.run.live_sample_period = n;
options.sample_period_set = true;
}
+ else if (!strcmp (long_options[option_index].name, "compact"))
+ options.writer.compact = true;
+ else if (!strcmp (long_options[option_index].name, "format"))
+ {
+ if (!strcmp (optarg, "binary"))
+ options.writer.text = false;
+ else if (!strcmp (optarg, "text"))
+ options.writer.text = true;
+ else
+ {
+ fprintf (stderr,
+ _("Invalid --format: expected 'binary' or 'text', "
+ "got: %s\n"),
+ optarg);
+ return false;
+ }
+ }
}
}
@@ -339,6 +373,13 @@ parse_arguments (int argc, char *argv[], program_options
&options)
return false;
}
+ if (options.writer.compact && options.writer.version < 4)
+ {
+ fprintf (stderr,
+ _("Error: --compact requires --gcov_version >= 4\n"));
+ return false;
+ }
+
return true;
}
@@ -361,9 +402,16 @@ static bool
write_gcov_output (const program_options &options,
const gcov::gcov_profile &profile)
{
- gcov::gcov_writer_options writer_options;
+ if (options.writer.text && options.writer.version < 4)
+ {
+ fprintf (stderr,
+ _("Error: --format text requires --gcov_version >= 4 "
+ "(v3 has no textual format)\n"));
+ return false;
+ }
+
std::unique_ptr<gcov::gcov_writer_base> generator
- = gcov::make_gcov_writer (options.output_file, &profile, writer_options);
+ = gcov::make_gcov_writer (options.output_file, &profile, options.writer);
if (!generator)
{
fprintf (stderr, _("Error: unsupported output profile format\n"));
diff --git a/afdo_tools/src/afdo_dump.cc b/afdo_tools/src/afdo_dump.cc
index 4d30bb0dde7..dc8f02bbeb2 100644
--- a/afdo_tools/src/afdo_dump.cc
+++ b/afdo_tools/src/afdo_dump.cc
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3. If not see
#include <cinttypes>
#include <cstdint>
#include <cstdio>
+#include <cstring>
#include <getopt.h>
#include <memory>
#include <string>
@@ -41,6 +42,7 @@ print_usage (const char *prog, FILE *stream)
"\n"
"Dump an AutoFDO profile in text form.\n"
"\n"
+ " --gcov_version 3|4 Require this input profile version\n"
" -h, --help Print this help, then exit\n"
" -v, --version Print version information, then exit\n"
"\nFor bug reporting instructions, please see:\n"
@@ -199,12 +201,15 @@ main (int argc, char *argv[])
{
afdo_init_libintl ();
+ int version = 0; /* 0 = auto-detect. */
+
static option long_options[]
- = {{"help", no_argument, nullptr, 'h'},
+ = {{"gcov_version", required_argument, nullptr, 0},
+ {"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)
+ int c, idx = 0;
+ while ((c = getopt_long_only (argc, argv, "hv", long_options, &idx)) != -1)
{
if (c == 'h')
{
@@ -222,7 +227,15 @@ main (int argc, char *argv[])
"PARTICULAR PURPOSE.\n"));
return 0;
}
- return 1;
+ if (c != 0)
+ return 1;
+ const char *name = long_options[idx].name;
+ if (!strcmp (name, "gcov_version")
+ && !gcov::parse_profile_version (optarg, version))
+ {
+ fprintf (stderr, _("Unsupported GCOV version: %s\n"), optarg);
+ return 1;
+ }
}
if (optind >= argc)
@@ -245,6 +258,13 @@ main (int argc, char *argv[])
fprintf (stderr, _("Error: Failed to read %s\n"), path);
return 1;
}
+ if (version != 0 && reader->get_version () != static_cast<uint32_t>
(version))
+ {
+ fprintf (stderr,
+ _("Error: profile version does not match --gcov_version\n"));
+ return 1;
+ }
+
const gcov::gcov_profile &p = *reader->get_profile ();
/* Roots ordered by descending total, then name. */
diff --git a/afdo_tools/src/afdo_merge.cc b/afdo_tools/src/afdo_merge.cc
index fa467764654..a29eeb58c40 100644
--- a/afdo_tools/src/afdo_merge.cc
+++ b/afdo_tools/src/afdo_merge.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3. If not see
#include "version.h"
#include <cstdio>
+#include <cstring>
#include <getopt.h>
#include <string>
#include <vector>
@@ -32,9 +33,16 @@ print_usage (const char *prog, FILE *stream)
"\n"
"Merge AutoFDO profiles (sum counts). With -o, a single "
"input is\n"
- "allowed.\n"
+ "allowed and is converted to the requested output "
+ "version/format.\n"
+ "Inputs may be v3 or v4 (binary/compact/text), "
+ "auto-detected.\n"
"Options:\n"
" -o, --output_file FILE Write merged profile to FILE\n"
+ " --gcov_version ARG Output version (3 or 4; "
+ "default 3)\n"
+ " --compact v4 output only: varint encoding\n"
+ " --format ARG v4 output: 'binary' (default) or 'text'\n"
" -h, --help Print this help, then exit\n"
" -v, --version Print version information, then exit\n"
"\nFor bug reporting instructions, please see:\n"
@@ -48,7 +56,13 @@ main (int argc, char *argv[])
afdo_init_libintl ();
std::string output_path;
+ int out_version = 3;
+ bool out_compact = false;
+ bool out_text = false;
static option longopts[] = {{"output_file", required_argument, nullptr, 'o'},
+ {"gcov_version", required_argument, nullptr, 0},
+ {"compact", no_argument, nullptr, 0},
+ {"format", required_argument, nullptr, 0},
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'v'},
{nullptr, 0, nullptr, 0}};
@@ -73,12 +87,49 @@ main (int argc, char *argv[])
"warranty; not even for MERCHANTABILITY or FITNESS FOR A "
"PARTICULAR PURPOSE.\n"));
return 0;
+ case 0:
+ {
+ const char *name = longopts[idx].name;
+ if (!strcmp (name, "gcov_version"))
+ {
+ if (!gcov::parse_profile_version (optarg, out_version))
+ {
+ fprintf (stderr, _("Unsupported GCOV version: %s\n"),
+ optarg);
+ return 1;
+ }
+ }
+ else if (!strcmp (name, "compact"))
+ out_compact = true;
+ else if (!strcmp (name, "format"))
+ {
+ if (!strcmp (optarg, "binary"))
+ out_text = false;
+ else if (!strcmp (optarg, "text"))
+ out_text = true;
+ else
+ {
+ fprintf (stderr,
+ _("Invalid --format: expected 'binary' or "
+ "'text'\n"));
+ return 1;
+ }
+ }
+ break;
+ }
default:
print_usage (argv[0], stderr);
return 1;
}
}
+ if (out_compact && out_version < 4)
+ {
+ fprintf (stderr,
+ _("Error: --compact requires --gcov_version >= 4\n"));
+ return 1;
+ }
+
std::vector<std::string> inputs;
for (int i = optind; i < argc; ++i)
inputs.emplace_back (argv[i]);
@@ -100,6 +151,9 @@ main (int argc, char *argv[])
}
gcov::gcov_merger merger;
+ merger.set_output_version (out_version);
+ merger.set_output_compact (out_compact);
+ merger.set_output_text (out_text);
if (!merger.merge (inputs, output_path))
return 1;
diff --git a/afdo_tools/src/gcov.h b/afdo_tools/src/gcov.h
index e5dd89b4a08..68d59b838a4 100644
--- a/afdo_tools/src/gcov.h
+++ b/afdo_tools/src/gcov.h
@@ -29,6 +29,9 @@ along with GCC; see the file COPYING3. If not see
namespace gcov {
+/* Parse a supported AutoFDO profile version number. */
+bool parse_profile_version (const char *arg, int &version);
+
/* All-ones sentinel for an absent name, file, section, or symbol index. */
constexpr uint32_t INVALID_INDEX = UINT32_MAX;
@@ -37,13 +40,7 @@ void gcov_note_count_overflow ();
/* Add profile counts with wrapping uint64 arithmetic, warning once if the
add overflows. */
-inline uint64_t
-gcov_add_count (uint64_t a, uint64_t b)
-{
- if (b > std::numeric_limits<uint64_t>::max () - a)
- gcov_note_count_overflow ();
- return a + b;
-}
+uint64_t gcov_add_count (uint64_t a, uint64_t b);
/* A symbol's name and defining source file. */
struct symbol_identity
@@ -232,32 +229,11 @@ struct gcov_location_inline : gcov_location
}
};
-/* Count contributed by this location: its own count for a normal or call
- location, or the subtree total for an inlined callee. */
-inline uint64_t
-gcov_location::total_count () const
-{
- if (type == kind::normal)
- return static_cast<const gcov_location_normal *> (this)->count;
- else if (type == kind::call)
- return static_cast<const gcov_location_call *> (this)->count;
- else
- return static_cast<const gcov_location_inline *> (this)
- ->inlined_sym.total_count;
-}
-
/* A function's total_count: the sum of its position counts plus the totals of
its inline subtrees. Canonical definition of gcov_function::total_count;
callers that build or mutate a function's locations recompute it via
this. */
-inline uint64_t
-gcov_function_subtree_count (const gcov_function &func)
-{
- uint64_t total = 0;
- for (const auto &loc : func.locations)
- total = gcov_add_count (total, loc->total_count ());
- return total;
-}
+uint64_t gcov_function_subtree_count (const gcov_function &func);
/* AutoFDO v3 on-disk format constants (magic, tags, version). */
namespace v3 {
@@ -288,6 +264,55 @@ struct format
} /* namespace v3. */
+/* AutoFDO v4 on-disk format constants. */
+namespace v4 {
+
+/* GCOV file format constants. */
+struct format
+{
+ enum : uint32_t
+ {
+ AFDO_GCOV_MAGIC = 0x67636F76, /* "gcov" */
+ AFDO_GCOV_VERSION = 4
+ };
+};
+
+/* One entry in the v4 section directory. */
+struct section_directory_entry
+{
+ uint64_t offset = 0;
+ uint64_t size = 0;
+ uint8_t bitmask = 0;
+};
+
+/* Section types. */
+enum section_type : uint8_t
+{
+ SECTION_STRING_TABLE = 0x01,
+ SECTION_SUMMARY_INFO = 0x02,
+ SECTION_FILE_NAMES = 0x03,
+ SECTION_SYMBOL_NAMES = 0x04,
+ SECTION_SYMBOL_INFO = 0x05,
+ SECTION_INLINE_INDEX_TABLE = 0x06,
+ SECTION_INLINE_INDICES = 0x07,
+ /* Bit 7: the section body uses compact (varint) encoding. */
+ SECTION_COMPACT_FLAG = 0x80,
+};
+
+/* Location types (bits 0-6 of the location bitmask). */
+enum location_kind : uint8_t
+{
+ LOCATION_ZERO = 0x01,
+ LOCATION_NORMAL = 0x02,
+ LOCATION_WIDE = 0x03,
+ LOCATION_CALLED_FN = 0x04,
+ LOCATION_CALLED_FNS = 0x05,
+ LOCATION_INLINED_FN = 0x06,
+ /* Bit 7: a discriminator word follows the line offset. */
+ LOCATION_DISC_FLAG = 0x80,
+};
+
+} /* namespace v4. */
} /* namespace gcov. */
#endif /* GCC_GCOV_H. */
diff --git a/afdo_tools/src/gcov_io.cc b/afdo_tools/src/gcov_io.cc
index 7618acea12c..c2b700d8600 100644
--- a/afdo_tools/src/gcov_io.cc
+++ b/afdo_tools/src/gcov_io.cc
@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see
#include <cstdio>
#include <cstring>
+#include <limits>
namespace gcov {
@@ -43,6 +44,47 @@ gcov_note_count_overflow ()
_("Warning: GCOV profile count overflowed a uint64_t\n"));
}
+uint64_t
+gcov_add_count (uint64_t a, uint64_t b)
+{
+ if (b > std::numeric_limits<uint64_t>::max () - a)
+ gcov_note_count_overflow ();
+ return a + b;
+}
+
+uint64_t
+gcov_location::total_count () const
+{
+ if (type == kind::normal)
+ return static_cast<const gcov_location_normal *> (this)->count;
+ if (type == kind::call)
+ return static_cast<const gcov_location_call *> (this)->count;
+ return static_cast<const gcov_location_inline *> (this)
+ ->inlined_sym.total_count;
+}
+
+uint64_t
+gcov_function_subtree_count (const gcov_function &func)
+{
+ uint64_t total = 0;
+ for (const auto &loc : func.locations)
+ total = gcov_add_count (total, loc->total_count ());
+ return total;
+}
+
+/* Parse a supported AutoFDO profile version number. */
+bool
+parse_profile_version (const char *arg, int &version)
+{
+ if (!strcmp (arg, "3"))
+ version = 3;
+ else if (!strcmp (arg, "4"))
+ version = 4;
+ else
+ return false;
+ return true;
+}
+
/* Write VALUE to FILE as a raw host-endian 32-bit word. */
void
gcov_write_uint32 (FILE *file, uint32_t value)
@@ -106,4 +148,259 @@ gcov_read_string (const uint8_t *&cur, const uint8_t
*end, std::string &out)
return true;
}
+namespace v4 {
+
+static bool g_decode_failed = false;
+static const char *g_decode_msg = "";
+
+void
+clear_decode_error ()
+{
+ g_decode_failed = false;
+ g_decode_msg = "";
+}
+
+/* Record MSG as the decode error. Only the first one is kept, as cascading
+ failures are less informative. */
+void
+set_decode_error (const char *msg)
+{
+ if (g_decode_failed)
+ return;
+ g_decode_failed = true;
+ g_decode_msg = msg;
+}
+
+bool
+decode_failed ()
+{
+ return g_decode_failed;
+}
+
+const char *
+decode_message ()
+{
+ return g_decode_msg;
+}
+
+void
+write_be8 (std::vector<uint8_t> &buf, uint8_t value)
+{
+ buf.push_back (value);
+}
+
+namespace {
+
+/* Append the low NBYTES of VALUE in big-endian form. */
+void
+write_be_bytes (std::vector<uint8_t> &buf, uint64_t value, unsigned nbytes)
+{
+ for (unsigned i = nbytes; i-- > 0;)
+ buf.push_back ((value >> (i * 8)) & 0xFF);
+}
+
+} /* anonymous namespace. */
+
+void
+write_be16 (std::vector<uint8_t> &buf, uint16_t value)
+{
+ write_be_bytes (buf, value, 2);
+}
+
+void
+write_be32 (std::vector<uint8_t> &buf, uint32_t value)
+{
+ write_be_bytes (buf, value, 4);
+}
+
+void
+write_be56 (std::vector<uint8_t> &buf, uint64_t value)
+{
+ write_be_bytes (buf, value, 7);
+}
+
+void
+write_be64 (std::vector<uint8_t> &buf, uint64_t value)
+{
+ write_be_bytes (buf, value, 8);
+}
+
+/* Append VALUE as a base-128 varint: 7 bits per byte, high bit set to
+ continue. */
+void
+write_varint (std::vector<uint8_t> &buf, uint64_t value)
+{
+ do
+ {
+ uint8_t byte = static_cast<uint8_t> (value & 0x7F);
+ value >>= 7;
+ if (value)
+ byte = static_cast<uint8_t> (byte | 0x80);
+ buf.push_back (byte);
+ }
+ while (value);
+}
+
+void
+write_str (std::vector<uint8_t> &buf, const std::string &str, bool compact)
+{
+ const uint32_t len
+ = static_cast<uint32_t> (str.size () + 1); /* Include NUL. */
+ write_int32 (buf, len, compact);
+ buf.insert (buf.end (), str.begin (), str.end ());
+ buf.push_back ('\0');
+}
+
+void
+write_line_offset (std::vector<uint8_t> &buf, uint32_t value, bool compact)
+{
+ if (compact)
+ write_varint (buf, value);
+ else
+ write_be_bytes (buf, value, 3);
+}
+
+void
+write_int (std::vector<uint8_t> &buf, uint64_t value, bool compact)
+{
+ if (compact)
+ write_varint (buf, value);
+ else
+ write_be64 (buf, value);
+}
+
+void
+write_int32 (std::vector<uint8_t> &buf, uint32_t value, bool compact)
+{
+ if (compact)
+ write_varint (buf, value);
+ else
+ write_be32 (buf, value);
+}
+
+uint8_t
+read_be8 (const uint8_t *&cur, const uint8_t *end)
+{
+ if (cur >= end)
+ {
+ set_decode_error ("v4: truncated uint8");
+ return 0;
+ }
+ return *cur++;
+}
+
+namespace {
+
+/* Read the low NBYTES as a big-endian value, advancing CUR. On truncation
+ record the decode error and return 0. */
+uint64_t
+read_be_bytes (const uint8_t *&cur, const uint8_t *end, unsigned nbytes)
+{
+ if (cur > end || nbytes > (size_t) (end - cur))
+ {
+ set_decode_error ("v4: truncated big-endian value");
+ return 0;
+ }
+ uint64_t val = 0;
+ for (unsigned i = 0; i < nbytes; i++)
+ val = (val << 8) | *cur++;
+ return val;
+}
+
+} /* anonymous namespace. */
+
+uint16_t
+read_be16 (const uint8_t *&cur, const uint8_t *end)
+{
+ return static_cast<uint16_t> (read_be_bytes (cur, end, 2));
+}
+
+uint32_t
+read_be32 (const uint8_t *&cur, const uint8_t *end)
+{
+ return static_cast<uint32_t> (read_be_bytes (cur, end, 4));
+}
+
+uint64_t
+read_be56 (const uint8_t *&cur, const uint8_t *end)
+{
+ return read_be_bytes (cur, end, 7);
+}
+
+uint64_t
+read_be64 (const uint8_t *&cur, const uint8_t *end)
+{
+ return read_be_bytes (cur, end, 8);
+}
+
+/* Read a base-128 varint, recording a decode error on truncation or on a
+ value wider than 64 bits. */
+uint64_t
+read_varint (const uint8_t *&cur, const uint8_t *end)
+{
+ uint64_t result = 0;
+ unsigned shift = 0;
+ while (cur < end)
+ {
+ uint8_t byte = *cur++;
+ result |= uint64_t (byte & 0x7F) << shift;
+ if ((byte & 0x80) == 0)
+ return result;
+ shift += 7;
+ if (shift >= 64)
+ {
+ set_decode_error ("v4: varint too long");
+ return result;
+ }
+ }
+ set_decode_error ("v4: truncated varint");
+ return result;
+}
+
+uint64_t
+read_wide_int (const uint8_t *&cur, const uint8_t *end, bool compact)
+{
+ return compact ? read_varint (cur, end) : read_be64 (cur, end);
+}
+
+uint32_t
+read_int32 (const uint8_t *&cur, const uint8_t *end, bool compact)
+{
+ return compact ? static_cast<uint32_t> (read_varint (cur, end))
+ : read_be32 (cur, end);
+}
+
+uint16_t
+read_disc (const uint8_t *&cur, const uint8_t *end, bool compact)
+{
+ return compact ? static_cast<uint16_t> (read_varint (cur, end))
+ : read_be16 (cur, end);
+}
+
+uint32_t
+read_line_offset (const uint8_t *&cur, const uint8_t *end, bool compact)
+{
+ if (compact)
+ return static_cast<uint32_t> (read_varint (cur, end));
+ return static_cast<uint32_t> (read_be_bytes (cur, end, 3));
+}
+
+/* Read a length-prefixed string, dropping the trailing NUL counted by the
+ length. */
+std::string
+read_str (const uint8_t *&cur, const uint8_t *end, bool compact)
+{
+ uint32_t len = read_int32 (cur, end, compact);
+ if (len == 0)
+ return "";
+ if (cur > end || len > (size_t) (end - cur) || cur[len - 1] != '\0')
+ {
+ set_decode_error ("v4: string past end of buffer");
+ return "";
+ }
+ std::string result (reinterpret_cast<const char *> (cur), len - 1);
+ cur += len;
+ return result;
+}
+} /* namespace v4. */
} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_io.h b/afdo_tools/src/gcov_io.h
index 9749e85cd75..b82e144fe92 100644
--- a/afdo_tools/src/gcov_io.h
+++ b/afdo_tools/src/gcov_io.h
@@ -38,6 +38,52 @@ bool gcov_read_uint64 (const uint8_t *&cur, const uint8_t
*end,
bool gcov_read_string (const uint8_t *&cur, const uint8_t *end,
std::string &out);
+/* GCOV v4 IO primitives: big-endian (normal) or varint (compact). Sections
are
+ assembled in memory and written once. */
+namespace v4 {
+
+void clear_decode_error ();
+void set_decode_error (const char *msg);
+bool decode_failed ();
+const char *decode_message ();
+
+/* Fixed-width big-endian writers. */
+void write_be8 (std::vector<uint8_t> &buf, uint8_t value);
+void write_be16 (std::vector<uint8_t> &buf, uint16_t value);
+void write_be32 (std::vector<uint8_t> &buf, uint32_t value);
+void write_be56 (std::vector<uint8_t> &buf, uint64_t value);
+void write_be64 (std::vector<uint8_t> &buf, uint64_t value);
+
+/* Protobuf-style varint writer. */
+void write_varint (std::vector<uint8_t> &buf, uint64_t value);
+
+/* Length-prefixed (len includes the trailing '\0'). */
+void write_str (std::vector<uint8_t> &buf, const std::string &str,
+ bool compact);
+/* 3-byte big-endian line offset (or varint when compact). */
+void write_line_offset (std::vector<uint8_t> &buf, uint32_t value,
+ bool compact);
+
+/* Compact-dispatching writers: varint when compact, big-endian otherwise. */
+void write_int (std::vector<uint8_t> &buf, uint64_t value, bool compact);
+void write_int32 (std::vector<uint8_t> &buf, uint32_t value, bool compact);
+
+/* Fixed-width big-endian readers. */
+uint8_t read_be8 (const uint8_t *&cur, const uint8_t *end);
+uint16_t read_be16 (const uint8_t *&cur, const uint8_t *end);
+uint32_t read_be32 (const uint8_t *&cur, const uint8_t *end);
+uint64_t read_be56 (const uint8_t *&cur, const uint8_t *end);
+uint64_t read_be64 (const uint8_t *&cur, const uint8_t *end);
+uint64_t read_varint (const uint8_t *&cur, const uint8_t *end);
+
+/* Compact-dispatching readers. */
+uint64_t read_wide_int (const uint8_t *&cur, const uint8_t *end, bool compact);
+uint32_t read_int32 (const uint8_t *&cur, const uint8_t *end, bool compact);
+uint16_t read_disc (const uint8_t *&cur, const uint8_t *end, bool compact);
+uint32_t read_line_offset (const uint8_t *&cur, const uint8_t *end,
+ bool compact);
+std::string read_str (const uint8_t *&cur, const uint8_t *end, bool compact);
+} /* namespace v4. */
} /* namespace gcov. */
#endif /* GCC_GCOV_IO_H. */
diff --git a/afdo_tools/src/gcov_merge.cc b/afdo_tools/src/gcov_merge.cc
index a9dcea14287..f34d8a1a456 100644
--- a/afdo_tools/src/gcov_merge.cc
+++ b/afdo_tools/src/gcov_merge.cc
@@ -436,6 +436,9 @@ gcov_merger::merge (const std::vector<std::string> &inputs,
result.get_functions ().size ());
gcov_writer_options options;
+ options.version = output_version;
+ options.compact = output_compact;
+ options.text = output_text;
std::unique_ptr<gcov_writer_base> writer
= make_gcov_writer (output, &result, options);
if (!writer)
diff --git a/afdo_tools/src/gcov_merge.h b/afdo_tools/src/gcov_merge.h
index 82f757e8273..4589135ba9a 100644
--- a/afdo_tools/src/gcov_merge.h
+++ b/afdo_tools/src/gcov_merge.h
@@ -37,6 +37,10 @@ class gcov_merger
std::map<symbol_identity, uint32_t> identity_to_id;
/* Merged top-level symbol identity -> index into result.functions. */
std::map<symbol_identity, size_t> fn_index;
+ /* Output selection (afdo_merge flags). */
+ int output_version = 3;
+ bool output_compact = false;
+ bool output_text = false;
std::vector<uint32_t> build_remap (const gcov_profile &src_profile) const;
std::unique_ptr<gcov_location>
@@ -51,6 +55,21 @@ class gcov_merger
const std::vector<uint32_t> &remap);
public:
+ /* Output controls (default: v3 binary). --compact / --format text require
+ output_version >= 4. */
+ void set_output_version (int v)
+ {
+ output_version = v;
+ }
+ void set_output_compact (bool c)
+ {
+ output_compact = c;
+ }
+ void set_output_text (bool t)
+ {
+ output_text = t;
+ }
+
bool merge (const std::vector<std::string> &inputs,
const std::string &output);
};
diff --git a/afdo_tools/src/gcov_reader.cc b/afdo_tools/src/gcov_reader.cc
index 21188bd8ccf..f0bf858719a 100644
--- a/afdo_tools/src/gcov_reader.cc
+++ b/afdo_tools/src/gcov_reader.cc
@@ -18,10 +18,10 @@ along with GCC; see the file COPYING3. If not see
#include "gcov_reader.h"
#include "gcov.h"
-#include "gcov_io.h"
+#include "file_mmap.h"
#include <cstdint>
-#include <cstdio>
+#include <cstring>
#include <string>
namespace gcov {
@@ -40,25 +40,58 @@ gcov_reader_base::~gcov_reader_base ()
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)
+ afdo_tools::file_mmap fb;
+ if (!fb.map (path))
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;
+ const uint8_t *head = fb.data ();
+ const size_t n = fb.size ();
+ if (n >= 9 && memcmp (head, "filenames", 9) == 0)
+ return profile_format::v4_text;
+ if (n >= 4)
+ {
+ /* v4 magic is a fixed big-endian byte sequence; v3 magic is a
+ host-endian (little on this host) word. */
+ const uint32_t be = (static_cast<uint32_t> (head[0]) << 24)
+ | (static_cast<uint32_t> (head[1]) << 16)
+ | (static_cast<uint32_t> (head[2]) << 8)
+ | static_cast<uint32_t> (head[3]);
+ if (be == v4::format::AFDO_GCOV_MAGIC)
+ return profile_format::v4_binary;
+ const uint32_t le = static_cast<uint32_t> (head[0])
+ | (static_cast<uint32_t> (head[1]) << 8)
+ | (static_cast<uint32_t> (head[2]) << 16)
+ | (static_cast<uint32_t> (head[3]) << 24);
+ if (le == 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;
+ return make_gcov_reader (path, detect_format (path));
+}
+
+/* Create a reader for FORMAT, or nullptr when the format is unknown. */
+std::unique_ptr<gcov_reader_base>
+make_gcov_reader (const std::string &path, profile_format format)
+{
+ switch (format)
+ {
+ case profile_format::v3_binary:
+ return std::make_unique<gcov_reader_v3> (path);
+ case profile_format::v4_binary:
+ return std::make_unique<gcov_reader_v4> (path);
+ case profile_format::v4_text:
+ {
+ auto reader = std::make_unique<gcov_reader_v4> (path);
+ reader->set_text_profile (true);
+ return reader;
+ }
+ default:
+ return nullptr;
+ }
}
} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_reader.h b/afdo_tools/src/gcov_reader.h
index 9cb97b14edd..29e757505b8 100644
--- a/afdo_tools/src/gcov_reader.h
+++ b/afdo_tools/src/gcov_reader.h
@@ -22,8 +22,10 @@ along with GCC; see the file COPYING3. If not see
#include "gcov.h"
#include <cstdint>
+#include <functional>
#include <memory>
#include <string>
+#include <utility>
#include <vector>
namespace gcov {
@@ -40,7 +42,14 @@ protected:
public:
gcov_reader_base (const std::string &path_);
virtual ~gcov_reader_base ();
+ virtual bool supports_text_profile () const = 0;
+ virtual bool set_text_profile (bool is_text = true) = 0;
virtual bool read () = 0;
+ virtual bool supports_partial_read () const { return false; }
+ virtual bool read (std::string, bool = false)
+ {
+ return false;
+ }
const gcov_profile *get_profile () const { return &profile; }
uint32_t get_version () const { return version; }
@@ -63,6 +72,73 @@ class gcov_reader_v3 : public gcov_reader_base
public:
explicit gcov_reader_v3 (const std::string &path_) : gcov_reader_base (path_)
{}
+ bool supports_text_profile () const override { return false; }
+ bool set_text_profile (bool = true) override { return false; }
+ /* v3 has no partial read. */
+ using gcov_reader_base::read;
+ bool read () override;
+};
+
+/* Reader for version 4 of the GCOV format. Parses the
+ section-based binary layout (normal or compact) or the textual format. */
+class gcov_reader_v4 : public gcov_reader_base
+{
+ bool is_text_ = false;
+ bool compact_ = false;
+ bool partial_ = false;
+ std::vector<std::string> partial_files_;
+ /* When set, read_locations calls this for every referenced symbol id so a
+ partial read can lazily materialize that id's name/file group. */
+ std::function<void (uint32_t)> lazy_resolve_;
+ /* Name-table length while parsing binary locations; 0 outside a read. */
+ uint32_t parse_name_count_ = 0;
+
+ /* FILE_NAMES entry: a file group's name plus the section indices of its
+ STRING_TABLE / SYMBOL_NAMES sub-sections and the symbol-id range it owns.
+ Shared by the full and lazy readers. */
+ struct file_entry
+ {
+ std::string name;
+ uint32_t string_table_section = 0;
+ uint32_t symbol_names_section = 0;
+ uint32_t symbol_id_begin = 0;
+ uint32_t symbol_id_end = 0;
+ };
+
+ /* Parse a v4 binary profile. Empty WANT_PATH is a full read; otherwise it
is
+ a partial, per-TU read for the file matching WANT_PATH (see the
+ definition). set_partial_files () selects an analogous partial read by a
+ file-name list. */
+ bool read_binary (const uint8_t *base, size_t size,
+ const std::string &want_path = std::string (),
+ bool is_realpath = false);
+ bool read_text (const uint8_t *base, size_t size);
+ void read_locations (const uint8_t *&cur, const uint8_t *end, bool compact,
+ gcov_function &func);
+
+ static void parse_summary (const uint8_t *&cur, const uint8_t *end,
+ bool compact, gcov_summary &s);
+ static void parse_file_entries (const uint8_t *&cur, const uint8_t *end,
+ bool compact, std::vector<file_entry> &fes);
+
+public:
+ explicit gcov_reader_v4 (const std::string &path_) : gcov_reader_base (path_)
+ {}
+ bool supports_text_profile () const override { return true; }
+ bool set_text_profile (bool is_text = true) override
+ {
+ is_text_ = is_text;
+ return true;
+ }
+ /* Restrict SYMBOL_INFO parsing to functions whose source file is in FILES.
+ Metadata (names/files) is still read in full. */
+ void set_partial_files (std::vector<std::string> files)
+ {
+ partial_files_ = std::move (files);
+ partial_ = true;
+ }
+ bool supports_partial_read () const override { return true; }
+ bool read (std::string path_, bool is_realpath = false) override;
bool read () override;
};
@@ -70,14 +146,19 @@ enum class profile_format
{
unknown,
v3_binary,
+ v4_binary,
+ v4_text,
};
-/* Sniff a profile file's format from its leading bytes. */
+/* Sniff a profile file's format from its leading bytes:
+ - v3: 'gcda'
+ - v4: 'gcov'
+ - v4 text: 'filenames'. */
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);
+std::unique_ptr<gcov_reader_base> make_gcov_reader (const std::string &path,
+ profile_format format);
} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_reader_v4.cc b/afdo_tools/src/gcov_reader_v4.cc
new file mode 100644
index 00000000000..7a7bb052a1a
--- /dev/null
+++ b/afdo_tools/src/gcov_reader_v4.cc
@@ -0,0 +1,952 @@
+/* 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"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <functional>
+#include <map>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace gcov {
+
+using v4::read_be16;
+using v4::read_be32;
+using v4::read_be56;
+using v4::read_be64;
+using v4::read_be8;
+using v4::read_disc;
+using v4::read_int32;
+using v4::read_line_offset;
+using v4::read_str;
+using v4::read_wide_int;
+using v4::section_directory_entry;
+
+namespace {
+
+/* Rebuild a per-TU string table (index -> string) from a serialized
+ path-compressed trie. TABLE is pre-sized by the caller to the section's
+ declared string count; the ids are consecutive, so a terminal id at or past
+ that size means the section is corrupt. */
+void
+deserialize_trie (const uint8_t *&cur, const uint8_t *end,
+ const std::string &prefix, std::vector<std::string> &table)
+{
+ const uint8_t flags = read_be8 (cur, end);
+ const bool is_terminal = (flags >> 7) & 1;
+ const uint8_t num_children = flags & 0x7F;
+
+ if (is_terminal)
+ {
+ const uint32_t tid = read_be32 (cur, end);
+ if (tid >= table.size ())
+ {
+ v4::set_decode_error ("v4: trie terminal id past string count");
+ return;
+ }
+ table[tid] = prefix;
+ }
+
+ for (uint8_t i = 0; i < num_children && !v4::decode_failed (); ++i)
+ {
+ const uint16_t label_len = read_be16 (cur, end);
+ if (cur > end || label_len > (size_t) (end - cur))
+ {
+ v4::set_decode_error ("v4: trie edge label past end");
+ return;
+ }
+ std::string label (reinterpret_cast<const char *> (cur), label_len);
+ cur += label_len;
+ deserialize_trie (cur, end, prefix + label, table);
+ }
+}
+
+} /* anonymous namespace. */
+
+/* num_locs, then each location. Builds gcov_location_{normal,call,inline}
into
+ FUNC.LOCATIONS. */
+void
+gcov_reader_v4::read_locations (const uint8_t *&cur, const uint8_t *end,
+ bool compact, gcov_function &func)
+{
+ const uint32_t num_locs = read_int32 (cur, end, compact);
+ func.locations.reserve (num_locs);
+ for (uint32_t i = 0; i < num_locs && !v4::decode_failed (); ++i)
+ {
+ const uint8_t bitmask = read_be8 (cur, end);
+ const uint8_t type = bitmask & 0x7F;
+ const bool has_disc = (bitmask >> 7) & 1;
+ const uint32_t line = read_line_offset (cur, end, compact);
+ const uint32_t disc = has_disc ? read_disc (cur, end, compact) : 0;
+
+ std::unique_ptr<gcov_location> loc;
+ switch (type)
+ {
+ case v4::LOCATION_ZERO:
+ loc = std::make_unique<gcov_location_normal> (0);
+ break;
+ case v4::LOCATION_NORMAL:
+ loc = std::make_unique<gcov_location_normal> (
+ read_int32 (cur, end, compact));
+ break;
+ case v4::LOCATION_WIDE:
+ loc = std::make_unique<gcov_location_normal> (
+ read_wide_int (cur, end, compact));
+ break;
+ case v4::LOCATION_CALLED_FN:
+ {
+ const uint32_t idx = read_int32 (cur, end, compact);
+ const uint64_t cnt = read_wide_int (cur, end, compact);
+ if (lazy_resolve_)
+ lazy_resolve_ (idx);
+ auto call = std::make_unique<gcov_location_call> (cnt);
+ call->callee_map[idx] = cnt;
+ loc = std::move (call);
+ break;
+ }
+ case v4::LOCATION_CALLED_FNS:
+ {
+ const uint32_t n = read_int32 (cur, end, compact);
+ auto call = std::make_unique<gcov_location_call> ();
+ uint64_t sum = 0;
+ for (uint32_t j = 0; j < n; ++j)
+ {
+ const uint32_t idx = read_int32 (cur, end, compact);
+ const uint64_t cnt = read_wide_int (cur, end, compact);
+ if (lazy_resolve_)
+ lazy_resolve_ (idx);
+ call->callee_map[idx] = cnt;
+ sum += cnt;
+ }
+ call->count = sum;
+ loc = std::move (call);
+ break;
+ }
+ case v4::LOCATION_INLINED_FN:
+ {
+ const uint32_t idx = read_int32 (cur, end, compact);
+ auto inl = std::make_unique<gcov_location_inline> ();
+ inl->inlined_sym.id = idx;
+ if (lazy_resolve_)
+ lazy_resolve_ (idx);
+ if (idx < parse_name_count_)
+ inl->inlined_sym.desc.name_index = idx;
+ read_locations (cur, end, compact, inl->inlined_sym);
+ inl->inlined_sym.total_count
+ = gcov_function_subtree_count (inl->inlined_sym);
+ loc = std::move (inl);
+ break;
+ }
+ default:
+ {
+ /* Forward-compat: unknown types carry a trailing-size word. */
+ const uint32_t trailing = read_int32 (cur, end, compact);
+ if (cur + trailing > end)
+ {
+ v4::set_decode_error ("v4: unknown location trailing past end");
+ return;
+ }
+ cur += trailing;
+ continue;
+ }
+ }
+ loc->offset = line;
+ loc->discriminator = disc;
+ func.locations.push_back (std::move (loc));
+ }
+}
+
+/* SUMMARY section body, shared by the full and lazy readers. */
+void
+gcov_reader_v4::parse_summary (const uint8_t *&cur, const uint8_t *end,
+ bool compact, gcov_summary &s)
+{
+ s.total_count = read_wide_int (cur, end, compact);
+ s.max_count = read_wide_int (cur, end, compact);
+ s.max_function_count = read_wide_int (cur, end, compact);
+ s.num_counts = read_wide_int (cur, end, compact);
+ s.num_functions = read_wide_int (cur, end, compact);
+ s.num_detailed_entries = read_wide_int (cur, end, compact);
+ /* Every entry occupies at least one byte, so a count past the remaining
+ input is corrupt; this also keeps the value within size_t. */
+ if (s.num_detailed_entries > static_cast<uint64_t> (end - cur))
+ {
+ v4::set_decode_error ("v4: detailed_entries count past end");
+ return;
+ }
+ s.detailed_entries.resize (static_cast<size_t> (s.num_detailed_entries));
+ for (auto &e : s.detailed_entries)
+ {
+ e.cutoff = read_int32 (cur, end, compact);
+ e.min_count = read_wide_int (cur, end, compact);
+ e.num_counts = read_wide_int (cur, end, compact);
+ }
+}
+
+/* FILE_NAMES section body, shared by the full and lazy readers. */
+void
+gcov_reader_v4::parse_file_entries (const uint8_t *&cur, const uint8_t *end,
+ bool compact, std::vector<file_entry> &fes)
+{
+ const uint32_t n = read_int32 (cur, end, compact);
+ fes.resize (n);
+ for (auto &fe : fes)
+ {
+ fe.name = read_str (cur, end, compact);
+ fe.string_table_section = read_int32 (cur, end, compact);
+ fe.symbol_names_section = read_int32 (cur, end, compact);
+ fe.symbol_id_begin = read_int32 (cur, end, compact);
+ fe.symbol_id_end = read_int32 (cur, end, compact);
+ }
+}
+
+/* Parse a v4 binary profile. With an empty WANT_PATH this is a full read: all
+ file groups are materialized and functions are emitted in section-index
order
+ (the writer's order, so re-emission is byte-identical). Otherwise it is a
+ partial, per-TU read: only the metadata plus the SYMBOL_INFO of the TU whose
+ file name matches WANT_PATH (by basename, or realpath when IS_REALPATH) and,
+ via the reverse inline index, its cross-module inliners. The per-TU
+ STRING_TABLE / SYMBOL_NAMES groups are then deserialized on demand.
+ set_partial_files () selects an analogous partial read keyed by a list of
+ file names rather than a single path. */
+bool
+gcov_reader_v4::read_binary (const uint8_t *base, size_t size,
+ const std::string &want_path, bool is_realpath)
+{
+ const uint8_t *cur = base, *end = base + size;
+ if (size < 16 || read_be32 (cur, end) != v4::format::AFDO_GCOV_MAGIC)
+ {
+ fprintf (stderr, "Error: invalid v4 magic\n");
+ return false;
+ }
+ magic = v4::format::AFDO_GCOV_MAGIC;
+ version = read_be32 (cur, end);
+ if (version != v4::format::AFDO_GCOV_VERSION)
+ {
+ fprintf (
+ stderr, "Error: unsupported v4 version %u\n", (unsigned) version);
+ return false;
+ }
+ read_be8 (
+ cur, end); /* Reserved byte; compactness is per-section (bitmask). */
+ const uint64_t total_sections = read_be56 (cur, end);
+ std::vector<section_directory_entry> section_directory (total_sections);
+ for (uint64_t i = 0; i < total_sections; ++i)
+ {
+ section_directory[i].offset = read_be64 (cur, end);
+ section_directory[i].size = read_be56 (cur, end);
+ section_directory[i].bitmask = read_be8 (cur, end);
+ }
+
+ auto section_bounds = [&] (uint64_t section_index,
+ const uint8_t *§ion_body,
+ const uint8_t *§ion_end,
+ bool &compact) -> bool {
+ if (section_index >= total_sections)
+ return false;
+ const section_directory_entry §ion = section_directory[section_index];
+ if (section.offset > size || section.size == 0
+ || section.size > size - section.offset)
+ return false;
+ const uint8_t *section_start = base + section.offset;
+ section_end = section_start + section.size;
+ compact = (section.bitmask & v4::SECTION_COMPACT_FLAG) != 0;
+ section_body = section_start + 1;
+ return true;
+ };
+
+ const bool partial = !want_path.empty () || partial_;
+
+ /* Phase A: the light metadata -- summary, file names, and the inline-index
+ directory. Per-TU STRING_TABLE / SYMBOL_NAMES are read by load_group. */
+ gcov_summary summary;
+ std::vector<file_entry> file_entries;
+ std::map<uint32_t, uint32_t> inline_index_sections;
+ for (uint64_t i = 0; i < total_sections; ++i)
+ {
+ const uint8_t *body, *section_end;
+ bool compact;
+ if (!section_bounds (i, body, section_end, compact))
+ continue;
+ const uint8_t section_type = section_directory[i].bitmask & 0x7F;
+ if (section_type == v4::SECTION_SUMMARY_INFO)
+ parse_summary (body, section_end, compact, summary);
+ else if (section_type == v4::SECTION_FILE_NAMES)
+ parse_file_entries (body, section_end, compact, file_entries);
+ else if (section_type == v4::SECTION_INLINE_INDEX_TABLE)
+ {
+ const uint32_t n = read_int32 (body, section_end, compact);
+ for (uint32_t k = 0; k < n; ++k)
+ {
+ const uint32_t file_id = read_int32 (body, section_end, compact);
+ const uint32_t section_index
+ = read_int32 (body, section_end, compact);
+ inline_index_sections[file_id] = section_index;
+ }
+ }
+ }
+
+ if (partial && file_entries.empty ())
+ {
+ fprintf (stderr, "Error: v4 partial read: no file names\n");
+ return false;
+ }
+
+ uint32_t total_symbols = 0;
+ for (const auto &fe : file_entries)
+ total_symbols = std::max (total_symbols, fe.symbol_id_end);
+ std::vector<std::string> function_names (total_symbols);
+ parse_name_count_ = total_symbols;
+ std::vector<uint32_t> file_name_map (total_symbols, INVALID_INDEX);
+ std::vector<std::string> file_names;
+ if (!file_entries.empty ())
+ {
+ file_names.resize (file_entries.size () - 1);
+ for (size_t g = 1; g < file_entries.size (); ++g)
+ file_names[g - 1] = file_entries[g].name;
+ }
+ std::vector<uint32_t> info_section (total_symbols, INVALID_INDEX);
+
+ std::vector<uint32_t> id_to_group (total_symbols, INVALID_INDEX);
+ for (uint32_t g = 0; g < file_entries.size (); ++g)
+ for (uint32_t id = file_entries[g].symbol_id_begin;
+ id < file_entries[g].symbol_id_end;
+ ++id)
+ if (id < total_symbols)
+ id_to_group[id] = g;
+
+ std::vector<bool> group_loaded (file_entries.size (), false);
+ auto load_group = [&] (uint32_t g)
+ {
+ if (g >= file_entries.size () || group_loaded[g])
+ return;
+ group_loaded[g] = true;
+ const file_entry &fe = file_entries[g];
+ std::vector<std::string> strings;
+ const uint8_t *body, *section_end;
+ bool compact;
+ if (section_bounds (fe.string_table_section, body, section_end, compact))
+ {
+ const uint32_t num_strings = read_int32 (body, section_end, compact);
+ strings.resize (num_strings);
+ deserialize_trie (body, section_end, "", strings);
+ }
+ if (section_bounds (fe.symbol_names_section, body, section_end, compact))
+ {
+ const uint32_t n = read_int32 (body, section_end, compact);
+ for (uint32_t k = 0; k < n; ++k)
+ {
+ const uint32_t nsi = read_int32 (body, section_end, compact);
+ const uint32_t sid = read_int32 (body, section_end, compact);
+ const uint32_t isec = read_int32 (body, section_end, compact);
+ if (sid >= total_symbols)
+ continue;
+ if (nsi < strings.size ())
+ function_names[sid] = strings[nsi];
+ file_name_map[sid] = (g == 0) ? INVALID_INDEX : (g - 1);
+ info_section[sid] = isec;
+ }
+ }
+ };
+
+ lazy_resolve_ = [&] (uint32_t id)
+ {
+ const uint32_t g
+ = id < id_to_group.size () ? id_to_group[id] : INVALID_INDEX;
+ if (g != INVALID_INDEX)
+ load_group (g);
+ };
+
+ /* Select which functions to parse. */
+ std::vector<uint32_t> wanted_ids;
+ if (!partial)
+ {
+ /* Full read: materialize every group and take every top-level symbol.
*/
+ for (uint32_t g = 0; g < file_entries.size (); ++g)
+ load_group (g);
+ for (uint32_t id = 0; id < total_symbols; ++id)
+ if (info_section[id] != INVALID_INDEX)
+ wanted_ids.push_back (id);
+ }
+ else
+ {
+ /* Partial read: the wanted groups are those whose file name matches
+ WANT_PATH (by basename, or realpath when IS_REALPATH), or -- when
+ driven by set_partial_files () -- those named in partial_files_. */
+ auto basename_of = [] (const std::string &s) -> std::string {
+ const size_t pos = s.find_last_of ('/');
+ return pos == std::string::npos ? s : s.substr (pos + 1);
+ };
+ const std::string want
+ = is_realpath ? want_path : basename_of (want_path);
+ auto group_wanted = [&] (const std::string &fn) -> bool {
+ if (!want_path.empty ())
+ return (is_realpath ? fn : basename_of (fn)) == want;
+ for (const std::string &w : partial_files_)
+ if (fn == w)
+ return true;
+ return false;
+ };
+ for (uint32_t g = 1; g < file_entries.size (); ++g)
+ {
+ if (!group_wanted (file_entries[g].name))
+ continue;
+ load_group (g);
+ for (uint32_t id = file_entries[g].symbol_id_begin;
+ id < file_entries[g].symbol_id_end;
+ ++id)
+ if (id < total_symbols && info_section[id] != INVALID_INDEX)
+ wanted_ids.push_back (id);
+ /* Pull this file's cross-module inliners via its INLINE_INDICES
+ sub-section, read on demand. */
+ auto inline_index_it = inline_index_sections.find (g);
+ const uint8_t *inline_body, *inline_end;
+ bool inline_compact;
+ if (inline_index_it != inline_index_sections.end ()
+ && section_bounds (inline_index_it->second,
+ inline_body,
+ inline_end,
+ inline_compact))
+ {
+ const uint32_t cnt
+ = read_int32 (inline_body, inline_end, inline_compact);
+ for (uint32_t k = 0; k < cnt; ++k)
+ {
+ const uint32_t inl
+ = read_int32 (inline_body, inline_end, inline_compact);
+ lazy_resolve_ (inl);
+ if (inl < total_symbols && info_section[inl] != INVALID_INDEX)
+ wanted_ids.push_back (inl);
+ }
+ }
+ }
+ }
+
+ /* Emit in section-index order -- the writer's order -- for both the full and
+ partial reads, so re-emission is byte-identical and the two paths behave
+ identically. Each symbol owns a distinct SYMBOL_INFO section, so this is
a
+ total order; dedup first, as a partial read can reach an inliner twice.
*/
+ std::sort (wanted_ids.begin (), wanted_ids.end ());
+ wanted_ids.erase (
+ std::unique (wanted_ids.begin (), wanted_ids.end ()), wanted_ids.end ());
+ std::sort (
+ wanted_ids.begin (), wanted_ids.end (), [&] (uint32_t a, uint32_t b)
+ {
+ return info_section[a] < info_section[b];
+ });
+
+ std::vector<gcov_function> functions;
+ functions.reserve (wanted_ids.size ());
+ for (uint32_t id : wanted_ids)
+ {
+ const uint8_t *body, *section_end;
+ bool compact;
+ if (!section_bounds (info_section[id], body, section_end, compact))
+ continue;
+ lazy_resolve_ (id);
+ gcov_function func;
+ func.id = id;
+ func.desc.name_index = id;
+ const uint32_t fidx = (id < file_name_map.size ())
+ ? file_name_map[id]
+ : INVALID_INDEX;
+ func.desc.file_index
+ = (fidx < file_names.size ()) ? fidx : INVALID_INDEX;
+ func.head_count = read_wide_int (body, section_end, compact);
+ func.timestamp = read_wide_int (body, section_end, compact);
+ read_locations (body, section_end, compact, func);
+ func.total_count = gcov_function_subtree_count (func);
+ functions.push_back (std::move (func));
+ }
+ lazy_resolve_ = nullptr;
+ parse_name_count_ = 0;
+ profile.set_summary (std::move (summary));
+ profile.set_names (std::move (file_names), std::move (function_names),
+ std::move (file_name_map));
+ profile.set_functions (std::move (functions));
+
+ if (v4::decode_failed ())
+ {
+ fprintf (stderr, "Error: %s\n", v4::decode_message ());
+ return false;
+ }
+ return true;
+}
+
+bool
+gcov_reader_v4::read (std::string path_, bool is_realpath)
+{
+ afdo_tools::file_mmap fb;
+ if (!fb.map (path))
+ {
+ fprintf (stderr, "Error: cannot mmap %s\n", path.c_str ());
+ return false;
+ }
+ v4::clear_decode_error ();
+ return read_binary (fb.data (), fb.size (), path_, is_realpath);
+}
+
+bool
+gcov_reader_v4::read ()
+{
+ afdo_tools::file_mmap fb;
+ if (!fb.map (path))
+ {
+ fprintf (stderr, "Error: cannot mmap %s\n", path.c_str ());
+ return false;
+ }
+ const uint8_t *base = fb.data ();
+ const size_t size = fb.size ();
+
+ /* The format was already established by detect_format () / make_gcov_reader,
+ which sets is_text_ for a textual profile; no need to re-sniff here. */
+ v4::clear_decode_error ();
+ if (is_text_)
+ return read_text (base, size);
+ return read_binary (base, size);
+}
+
+namespace {
+
+/* Minimal recursive-descent parser for the v4 textual format. Symbols are
+ keyed by their name-pool index (the id in the header), so the parser fills
+ id -> name / id -> file maps and builds the gcov_function location trees;
+ the reader assembles the gcov_profile from those. */
+struct text_parser
+{
+ const char *cur;
+ const char *end;
+ std::map<uint32_t, std::string> id_name;
+ std::map<uint32_t, int64_t> id_file; /* -1 => unknown. */
+
+ text_parser (const char *b, const char *e)
+ : cur (b), end (e)
+ {
+ }
+
+ /* Every consuming method early-returns a default once !ok (). */
+ void fail (const char *what)
+ {
+ v4::set_decode_error (what);
+ }
+ bool ok () const { return !v4::decode_failed (); }
+ void skip_ws ()
+ {
+ while (cur < end
+ && (*cur == ' ' || *cur == '\n' || *cur == '\t' || *cur == '\r'))
+ ++cur;
+ }
+ bool peek (char c)
+ {
+ skip_ws ();
+ return cur < end && *cur == c;
+ }
+ void expect (char c)
+ {
+ if (!ok ())
+ return;
+ skip_ws ();
+ if (cur >= end || *cur != c)
+ {
+ fail ("v4 text: unexpected character");
+ return;
+ }
+ ++cur;
+ }
+ void expect_word (const char *w)
+ {
+ if (!ok ())
+ return;
+ skip_ws ();
+ const size_t n = strlen (w);
+ /* The buffer is not NUL-terminated at the keyword, so compare a bounded
+ span with strncmp rather than strcmp. */
+ if (static_cast<size_t> (end - cur) < n || strncmp (cur, w, n) != 0)
+ {
+ fail ("v4 text: expected keyword");
+ return;
+ }
+ cur += n;
+ }
+ uint64_t parse_uint ()
+ {
+ if (!ok ())
+ return 0;
+ skip_ws ();
+ if (cur >= end || *cur < '0' || *cur > '9')
+ {
+ fail ("v4 text: expected number");
+ return 0;
+ }
+ const char *start = cur;
+ while (cur < end && *cur >= '0' && *cur <= '9')
+ ++cur;
+ /* strtoull does not honour END and needs a NUL terminator, so convert the
+ bounded digit span from a temporary. */
+ const std::string digits (start, cur);
+ return strtoull (digits.c_str (), nullptr, 10);
+ }
+ int64_t parse_int ()
+ {
+ if (!ok ())
+ return 0;
+ skip_ws ();
+ const char *start = cur;
+ if (cur < end && *cur == '-')
+ ++cur;
+ if (cur >= end || *cur < '0' || *cur > '9')
+ {
+ fail ("v4 text: expected number");
+ return 0;
+ }
+ while (cur < end && *cur >= '0' && *cur <= '9')
+ ++cur;
+ const std::string digits (start, cur);
+ return strtoll (digits.c_str (), nullptr, 10);
+ }
+ std::string parse_quoted ()
+ {
+ expect ('"');
+ if (!ok ())
+ return std::string ();
+ const char *s = cur;
+ while (cur < end && *cur != '"')
+ ++cur;
+ if (cur >= end)
+ {
+ fail ("v4 text: unterminated string");
+ return std::string ();
+ }
+ std::string r (s, cur);
+ ++cur; /* Closing quote. */
+ return r;
+ }
+ std::string parse_word ()
+ {
+ if (!ok ())
+ return std::string ();
+ skip_ws ();
+ const char *s = cur;
+ while (cur < end && ((*cur >= 'a' && *cur <= 'z') || *cur == '_'))
+ ++cur;
+ return std::string (s, cur);
+ }
+ /* LOCATION = number (. number)? */
+ void parse_loc (uint32_t *line, uint32_t *disc)
+ {
+ *line = static_cast<uint32_t> (parse_uint ());
+ *disc = 0;
+ if (ok () && cur < end && *cur == '.')
+ {
+ ++cur;
+ *disc = static_cast<uint32_t> (parse_uint ());
+ }
+ }
+
+ /* Parse the section list of a function body into FUNC.LOCATIONS. */
+ void parse_sections (gcov_function &func)
+ {
+ expect ('{');
+ while (!peek ('}') && ok ())
+ {
+ const std::string sect = parse_word ();
+ expect ('=');
+ expect ('{');
+ if (sect == "locations")
+ {
+ while (!peek ('}') && ok ())
+ {
+ uint32_t line, disc;
+ parse_loc (&line, &disc);
+ expect ('=');
+ const uint64_t cnt = parse_uint ();
+ auto loc = std::make_unique<gcov_location_normal> (cnt);
+ loc->offset = line;
+ loc->discriminator = disc;
+ func.locations.push_back (std::move (loc));
+ if (peek (','))
+ expect (',');
+ }
+ expect ('}');
+ }
+ else if (sect == "callsites")
+ {
+ while (!peek ('}') && ok ())
+ {
+ uint32_t line, disc;
+ parse_loc (&line, &disc);
+ expect ('-');
+ expect ('>');
+ expect ('{');
+ auto call = std::make_unique<gcov_location_call> ();
+ uint64_t sum = 0;
+ while (!peek ('}') && ok ())
+ {
+ /* "name":id = count. Register the callee name so the pool
+ covers ICP-only ids (else the name is lost and the id can
+ exceed the name pool). */
+ const std::string tname = parse_quoted ();
+ expect (':');
+ const uint64_t id = parse_uint ();
+ expect ('=');
+ const uint64_t c = parse_uint ();
+ if (id_name.find (id) == id_name.end ())
+ id_name[id] = tname;
+ call->callee_map[id] = c;
+ sum += c;
+ if (peek (','))
+ expect (',');
+ }
+ expect ('}');
+ call->count = sum;
+ call->offset = line;
+ call->discriminator = disc;
+ func.locations.push_back (std::move (call));
+ if (peek (','))
+ expect (',');
+ }
+ expect ('}');
+ }
+ else if (sect == "inlined")
+ {
+ while (!peek ('}') && ok ())
+ {
+ uint32_t line, disc;
+ parse_loc (&line, &disc);
+ expect ('=');
+ auto inl = std::make_unique<gcov_location_inline> ();
+ parse_inlined_header (inl->inlined_sym);
+ parse_sections (inl->inlined_sym);
+ inl->inlined_sym.total_count
+ = gcov_function_subtree_count (inl->inlined_sym);
+ inl->offset = line;
+ inl->discriminator = disc;
+ func.locations.push_back (std::move (inl));
+ if (peek (','))
+ expect (',');
+ }
+ expect ('}');
+ }
+ else
+ {
+ /* Forward-compat: skip an unknown section by brace matching. */
+ int depth = 1;
+ while (cur < end && depth > 0)
+ {
+ if (*cur == '{')
+ ++depth;
+ else if (*cur == '}')
+ --depth;
+ ++cur;
+ }
+ }
+ if (peek (','))
+ expect (',');
+ }
+ expect ('}');
+ }
+
+ /* "name":file_id(id:total) for an inlined callee. */
+ void parse_inlined_header (gcov_function &func)
+ {
+ const std::string name = parse_quoted ();
+ expect (':');
+ const int64_t file_id = parse_int ();
+ expect ('(');
+ const uint32_t id = static_cast<uint32_t> (parse_uint ());
+ expect (':');
+ (void) parse_uint (); /* total (recomputed) */
+ expect (')');
+ func.id = id;
+ id_name[id] = name;
+ id_file[id] = file_id;
+ expect ('=');
+ }
+
+ /* "name":file_id(id:total:head:ts) = { ... } for a top-level function. */
+ bool parse_symbol (gcov_function &func)
+ {
+ skip_ws ();
+ if (cur >= end || *cur != '"')
+ return false;
+ const std::string name = parse_quoted ();
+ expect (':');
+ const int64_t file_id = parse_int ();
+ expect ('(');
+ const uint32_t id = static_cast<uint32_t> (parse_uint ());
+ expect (':');
+ (void) parse_uint (); /* total (recomputed) */
+ expect (':');
+ const uint64_t head = parse_uint ();
+ expect (':');
+ const uint64_t ts = parse_uint ();
+ expect (')');
+ expect ('=');
+ func.id = id;
+ func.head_count = head;
+ func.timestamp = ts;
+ id_name[id] = name;
+ id_file[id] = file_id;
+ parse_sections (func);
+ return true;
+ }
+};
+
+} /* anonymous namespace. */
+
+bool
+gcov_reader_v4::read_text (const uint8_t *base, size_t size)
+{
+ text_parser p (reinterpret_cast<const char *> (base),
+ reinterpret_cast<const char *> (base) + size);
+
+ /* filenames = { "a", "b", ... } */
+ p.expect_word ("filenames");
+ p.expect ('=');
+ p.expect ('{');
+ std::vector<std::string> file_names;
+ while (!p.peek ('}') && p.ok ())
+ {
+ file_names.push_back (p.parse_quoted ());
+ if (p.peek (','))
+ p.expect (',');
+ }
+ p.expect ('}');
+
+ /* summary = { ... } */
+ p.expect_word ("summary");
+ p.expect ('=');
+ p.expect ('{');
+ gcov_summary s;
+ auto kv = [&] (const char *key) -> uint64_t {
+ p.expect_word (key);
+ p.expect ('=');
+ const uint64_t v = p.parse_uint ();
+ if (p.peek (','))
+ p.expect (',');
+ return v;
+ };
+ s.total_count = kv ("total_count");
+ s.max_count = kv ("max_count");
+ s.max_function_count = kv ("max_fn_count");
+ s.num_counts = kv ("num_counts");
+ s.num_functions = kv ("num_functions");
+ s.num_detailed_entries = kv ("num_detailed_entries");
+ p.expect_word ("detailed_entries");
+ p.expect ('=');
+ p.expect ('{');
+ while (!p.peek ('}') && p.ok ())
+ {
+ p.expect ('{');
+ gcov_summary_detailed_entry e{};
+ p.expect_word ("cutoff");
+ p.expect ('=');
+ e.cutoff = static_cast<uint32_t> (p.parse_uint ());
+ p.expect (',');
+ p.expect_word ("min_count");
+ p.expect ('=');
+ e.min_count = p.parse_uint ();
+ p.expect (',');
+ p.expect_word ("num_counts");
+ p.expect ('=');
+ e.num_counts = p.parse_uint ();
+ p.expect ('}');
+ s.detailed_entries.push_back (e);
+ if (p.peek (','))
+ p.expect (',');
+ }
+ p.expect ('}'); /* detailed_entries. */
+ p.expect ('}'); /* summary. */
+
+ /* Symbols. */
+ std::vector<gcov_function> funcs;
+ while (p.ok ())
+ {
+ p.skip_ws ();
+ if (p.cur >= p.end)
+ break;
+ gcov_function func;
+ if (!p.parse_symbol (func))
+ break;
+ func.total_count = gcov_function_subtree_count (func);
+ funcs.push_back (std::move (func));
+ }
+
+ if (v4::decode_failed ())
+ {
+ fprintf (stderr, "Error: %s\n", v4::decode_message ());
+ return false;
+ }
+
+ /* Size the name/file pools from every id seen (top-level + inlined). */
+ uint32_t max_id = 0;
+ for (const auto &kvp : p.id_name)
+ max_id = std::max (max_id, kvp.first);
+ std::vector<std::string> function_names (max_id + 1);
+ std::vector<uint32_t> file_name_map (max_id + 1, INVALID_INDEX);
+ for (const auto &kvp : p.id_name)
+ function_names[kvp.first] = kvp.second;
+ for (const auto &kvp : p.id_file)
+ file_name_map[kvp.first]
+ = kvp.second < 0 ? INVALID_INDEX : static_cast<uint32_t> (kvp.second);
+
+ /* Inlined nodes were built before the name pool existed. Set their display
+ names now (the binary reader does this inline). */
+ std::function<void (gcov_function &)> fixup_inlined_names
+ = [&] (gcov_function &f) {
+ for (auto &loc : f.locations)
+ if (loc->type == gcov_location::kind::inlined)
+ {
+ auto *inl = static_cast<gcov_location_inline *> (loc.get ());
+ if (inl->inlined_sym.id < function_names.size ())
+ inl->inlined_sym.desc.name_index = inl->inlined_sym.id;
+ fixup_inlined_names (inl->inlined_sym);
+ }
+ };
+
+ for (auto &func : funcs)
+ {
+ fixup_inlined_names (func);
+ func.desc.name_index = func.id;
+ const uint32_t fidx
+ = (func.id < file_name_map.size ()) ? file_name_map[func.id]
+ : INVALID_INDEX;
+ func.desc.file_index
+ = (fidx < file_names.size ()) ? fidx : INVALID_INDEX;
+ }
+
+ profile.set_summary (std::move (s));
+ profile.set_names (std::move (file_names), std::move (function_names),
+ std::move (file_name_map));
+ profile.set_functions (std::move (funcs));
+
+ version = v4::format::AFDO_GCOV_VERSION;
+ return true;
+}
+
+} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_writer.cc b/afdo_tools/src/gcov_writer.cc
index 70660e505a3..cda3bf7524b 100644
--- a/afdo_tools/src/gcov_writer.cc
+++ b/afdo_tools/src/gcov_writer.cc
@@ -61,9 +61,15 @@ 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)
+ if (options.version == 3 && !options.compact && !options.text)
return std::make_unique<gcov_writer_v3> (path, profile);
- return nullptr;
+ if (options.version != 4)
+ return nullptr;
+
+ auto writer = std::make_unique<gcov_writer_v4> (path, profile);
+ writer->set_compact (options.compact);
+ writer->set_text_profile (options.text);
+ return writer;
}
} /* namespace gcov. */
diff --git a/afdo_tools/src/gcov_writer.h b/afdo_tools/src/gcov_writer.h
index f6a5675cc13..7bd5290b6b8 100644
--- a/afdo_tools/src/gcov_writer.h
+++ b/afdo_tools/src/gcov_writer.h
@@ -23,12 +23,22 @@ along with GCC; see the file COPYING3. If not see
#include <cstdint>
#include <cstdio>
+#include <map>
#include <memory>
+#include <set>
#include <string>
+#include <unordered_map>
#include <vector>
namespace gcov {
+/* Writer-only named constants. */
+
+/* Trie node header byte: the high bit flags a terminal node and the low seven
+ bits hold the child count, so a node may have at most 127 children. */
+constexpr uint8_t TRIE_TERMINAL_FLAG = 0x80;
+constexpr size_t TRIE_MAX_CHILDREN = 0x7f;
+
class gcov_writer_base
{
protected:
@@ -43,12 +53,16 @@ public:
gcov_writer_base (const std::string &path, const gcov_profile *profile_);
virtual ~gcov_writer_base ();
+ virtual bool supports_text_profile () const = 0;
+ virtual bool set_text_profile (bool is_text = true) = 0;
virtual bool write () = 0;
};
struct gcov_writer_options
{
int version = 3;
+ bool compact = false;
+ bool text = false;
};
class gcov_writer_v3 : public gcov_writer_base
@@ -66,6 +80,112 @@ public:
gcov_writer_v3 (const std::string &path, const gcov_profile *profile_)
: gcov_writer_base (path, profile_)
{}
+ bool supports_text_profile () const override { return false; }
+ bool set_text_profile (bool = true) override { return false; }
+ bool write () override;
+};
+
+/* Version 4 GCOV writer. Emits the section-based binary layout (big-endian,
or
+ varint when compact) or the human-readable text format. */
+class gcov_writer_v4 : public gcov_writer_base
+{
+ /* Per-TU path-compressed string-table trie. */
+ struct trie_node
+ {
+ std::map<unsigned char, uint32_t> children;
+ bool is_terminal = false;
+ uint32_t terminal_id = 0;
+ };
+ struct trie
+ {
+ std::vector<trie_node> nodes; /* Root is nodes[0]. */
+ uint32_t next_id = 0;
+ trie ()
+ {
+ nodes.emplace_back ();
+ }
+ /* Insert S. Return its (stable) string index within this TU. */
+ uint32_t intern (const std::string &s);
+ };
+
+ /* One translation unit: its symbols (name-pool indices), trie, id range and
+ the section indices it occupies. */
+ struct file_group
+ {
+ std::string filename;
+ std::vector<uint32_t> symbol_indices;
+ trie string_trie;
+ uint32_t symbol_id_begin = 0;
+ uint32_t symbol_id_end = 0;
+ uint32_t string_table_section = 0;
+ uint32_t symbol_names_section = 0;
+ };
+
+ bool compact = false;
+ bool is_text = false;
+ std::vector<file_group> file_groups;
+ /* Name-pool index -> SYMBOL_INFO section index (top-level functions
+ only). */
+ std::unordered_map<uint32_t, uint32_t> symbol_to_section;
+ /* Name-pool index -> new contiguous symbol id. */
+ std::unordered_map<uint32_t, uint32_t> old_to_new_id;
+
+ void build_file_groups ();
+ void serialize_trie_node (std::vector<uint8_t> &buf, const trie &trie,
+ uint32_t node_idx) const;
+ std::vector<uint8_t> build_summary_section () const;
+ std::vector<uint8_t> build_file_names_section () const;
+ std::vector<uint8_t> build_symbol_names_section (size_t group_idx);
+ std::vector<uint8_t> build_string_table_section (size_t group_idx) const;
+ std::vector<uint8_t>
+ build_symbol_info_section (const gcov_function &func) const;
+ /* Reverse inline index, split per file group. */
+ std::vector<std::pair<uint32_t, std::vector<uint32_t>>>
+ compute_inline_groups () const;
+ /* Recurse F's inline tree, recording TOP_NEW_ID as a cross-group inliner of
+ each group (per GROUP_OF) other than TOP_GROUP that it inlines from. */
+ void collect_cross_group_inliners (
+ const gcov_function &f, uint32_t top_new_id, uint32_t top_group,
+ const std::unordered_map<uint32_t, uint32_t> &group_of,
+ std::vector<std::set<uint32_t>> &inliners) const;
+ std::vector<uint8_t>
+ build_inline_index_section (const std::vector<uint32_t> &ids) const;
+ std::vector<uint8_t> build_inline_index_table_section (
+ const std::vector<std::pair<uint32_t, uint32_t>> &entries) const;
+ void write_symbol_info_body (std::vector<uint8_t> &buf,
+ const gcov_function &func) const;
+ void write_inlined_body (std::vector<uint8_t> &buf,
+ const gcov_function &func) const;
+ void write_location_prefix (std::vector<uint8_t> &buf, uint8_t type,
+ uint32_t line, uint32_t disc) const;
+ void write_position_location (std::vector<uint8_t> &buf,
+ const gcov_location *loc) const;
+ bool write_binary ();
+ bool write_text ();
+ /* Text-format symbol id -> file-table index, or -1 when unknown. */
+ int32_t file_id_for (uint32_t id) const;
+ /* Emit FUNC's locations/callsites/inlined sections (text format) at INDENT.
+ */
+ void write_text_sections (FILE *out, const gcov_function &func,
+ int indent) const;
+ /* Emit one inlined callsite CS (text format) at INDENT. */
+ void write_text_inlined (FILE *out, const gcov_location_inline *cs,
+ int indent) const;
+
+public:
+ gcov_writer_v4 (const std::string &path, const gcov_profile *profile_)
+ : gcov_writer_base (path, profile_)
+ {}
+ bool supports_text_profile () const override { return true; }
+ bool set_text_profile (bool is_text_ = true) override
+ {
+ is_text = is_text_;
+ return true;
+ }
+ void set_compact (bool compact_ = true)
+ {
+ compact = compact_;
+ }
bool write () override;
};
diff --git a/afdo_tools/src/gcov_writer_v4.cc b/afdo_tools/src/gcov_writer_v4.cc
new file mode 100644
index 00000000000..4d25cfd17be
--- /dev/null
+++ b/afdo_tools/src/gcov_writer_v4.cc
@@ -0,0 +1,802 @@
+/* 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 "afdo_assert.h"
+#include "gcov.h"
+#include "gcov_io.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cstdint>
+#include <cstdio>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace gcov {
+
+using v4::write_be16;
+using v4::write_be32;
+using v4::write_be8;
+using v4::write_int;
+using v4::write_int32;
+using v4::write_line_offset;
+using v4::write_str;
+using v4::write_varint;
+using v4::section_directory_entry;
+
+namespace {
+
+/* Append SEC to FILE at the current offset, recording its directory entry in
+ DIR. Returns false (recording nothing) when the file position cannot be
+ read, so the caller can abort. */
+bool
+emit_section (FILE *file, const std::vector<uint8_t> &sec,
+ std::vector<section_directory_entry> &dir)
+{
+ const long pos = ftell (file);
+ if (pos < 0)
+ return false;
+ dir.push_back ({static_cast<uint64_t> (pos), sec.size (), sec[0]});
+ fwrite (sec.data (), 1, sec.size (), file);
+ return true;
+}
+
+/* "line[.disc]" for LOC's offset and (16-bit) discriminator. */
+std::string
+fmt_loc (const gcov_location *loc)
+{
+ const uint32_t disc = loc->discriminator & 0xffffu;
+ std::string s = std::to_string (loc->offset);
+ if (disc)
+ s += "." + std::to_string (disc);
+ return s;
+}
+
+/* Sum of F's position-location counts (not inline subtrees). */
+uint64_t
+text_position_count (const gcov_function &f)
+{
+ uint64_t t = 0;
+ for (const auto &loc : f.locations)
+ if (loc->type != gcov_location::kind::inlined)
+ t += loc->total_count ();
+ return t;
+}
+
+} /* Anonymous namespace. */
+
+uint32_t
+gcov_writer_v4::trie::intern (const std::string &s)
+{
+ uint32_t idx = 0;
+ for (unsigned char ch : s)
+ {
+ auto it = nodes[idx].children.find (ch);
+ if (it == nodes[idx].children.end ())
+ {
+ const uint32_t new_idx = static_cast<uint32_t> (nodes.size ());
+ nodes[idx].children[ch] = new_idx;
+ nodes.emplace_back ();
+ idx = new_idx;
+ }
+ else
+ idx = it->second;
+ }
+ trie_node &node = nodes[idx];
+ if (!node.is_terminal)
+ {
+ node.is_terminal = true;
+ node.terminal_id = next_id++;
+ }
+ return node.terminal_id;
+}
+
+/* Group the name pool by source file: the "unknown" group (no file) first,
then
+ one group per file-table entry, so the per-TU section ordering is
+ deterministic. */
+void
+gcov_writer_v4::build_file_groups ()
+{
+ file_groups.clear ();
+
+ file_group unknown;
+ unknown.filename = "";
+
+ std::vector<file_group> by_file (profile->get_file_names ().size ());
+ for (size_t i = 0; i < profile->get_file_names ().size (); ++i)
+ by_file[i].filename = profile->get_file_names ()[i];
+
+ for (uint32_t i = 0; i < profile->get_function_names ().size (); ++i)
+ {
+ const uint32_t file_idx
+ = (i < profile->get_file_name_map ().size ())
+ ? profile->get_file_name_map ()[i]
+ : INVALID_INDEX;
+ if (file_idx == INVALID_INDEX
+ || file_idx >= profile->get_file_names ().size ())
+ unknown.symbol_indices.push_back (i);
+ else
+ by_file[file_idx].symbol_indices.push_back (i);
+ }
+
+ file_groups.push_back (std::move (unknown));
+ for (auto &g : by_file)
+ file_groups.push_back (std::move (g));
+}
+
+/* Serialize TRIE rooted at NODE_IDX:
+ 1 byte (terminal flag | child count),
+ 4 byte terminal id (optional)
+ for each child:
+ 2 byte edge-label length,
+ label bytes path-compressed
+ ... and the child node recursively.
+
+ Node bitmask / terminal id / label length are always big-endian, even in
+ compact mode (only the leading num_strings word is compact-dispatched). */
+void
+gcov_writer_v4::serialize_trie_node (std::vector<uint8_t> &buf,
+ const trie &trie, uint32_t node_idx) const
+{
+ const trie_node &node = trie.nodes[node_idx];
+ /* The child count shares its header byte with the terminal flag, so it must
+ fit in seven bits. */
+ afdo_assert (node.children.size () <= TRIE_MAX_CHILDREN);
+ write_be8 (buf,
+ static_cast<uint8_t> ((node.is_terminal ? TRIE_TERMINAL_FLAG : 0)
+ | node.children.size ()));
+ if (node.is_terminal)
+ write_be32 (buf, node.terminal_id);
+
+ for (const auto &child : node.children)
+ {
+ const unsigned char ch = child.first;
+ uint32_t cur = child.second;
+ /* Path-compress single-child non-terminal chains into one edge label.
+ The common case is a single-character edge: emit it directly and only
+ build the LABEL string when the compressed edge is longer. */
+ if (trie.nodes[cur].children.size () != 1 || trie.nodes[cur].is_terminal)
+ {
+ write_be16 (buf, 1);
+ buf.push_back (static_cast<uint8_t> (ch));
+ }
+ else
+ {
+ std::string label (1, static_cast<char> (ch));
+ do
+ {
+ const auto &next = *trie.nodes[cur].children.begin ();
+ label += static_cast<char> (next.first);
+ cur = next.second;
+ }
+ while (trie.nodes[cur].children.size () == 1
+ && !trie.nodes[cur].is_terminal);
+ write_be16 (buf, static_cast<uint16_t> (label.size ()));
+ buf.insert (buf.end (), label.begin (), label.end ());
+ }
+ serialize_trie_node (buf, trie, cur);
+ }
+}
+
+std::vector<uint8_t>
+gcov_writer_v4::build_summary_section () const
+{
+ std::vector<uint8_t> buf;
+ const gcov_summary &s = profile->get_summary ();
+ write_be8 (
+ buf, v4::SECTION_SUMMARY_INFO | (compact ? v4::SECTION_COMPACT_FLAG : 0));
+ write_int (buf, s.total_count, compact);
+ write_int (buf, s.max_count, compact);
+ write_int (buf, s.max_function_count, compact);
+ write_int (buf, s.num_counts, compact);
+ write_int (buf, s.num_functions, compact);
+ write_int (buf, s.num_detailed_entries, compact);
+ for (const auto &e : s.detailed_entries)
+ {
+ write_int32 (buf, e.cutoff, compact);
+ write_int (buf, e.min_count, compact);
+ write_int (buf, e.num_counts, compact);
+ }
+ return buf;
+}
+
+std::vector<uint8_t>
+gcov_writer_v4::build_file_names_section () const
+{
+ std::vector<uint8_t> buf;
+ write_be8 (
+ buf, v4::SECTION_FILE_NAMES | (compact ? v4::SECTION_COMPACT_FLAG : 0));
+ write_int32 (buf, static_cast<uint32_t> (file_groups.size ()), compact);
+ for (const auto &fg : file_groups)
+ {
+ write_str (buf, fg.filename, compact);
+ write_int32 (buf, fg.string_table_section, compact);
+ write_int32 (buf, fg.symbol_names_section, compact);
+ write_int32 (buf, fg.symbol_id_begin, compact);
+ write_int32 (buf, fg.symbol_id_end, compact);
+ }
+ return buf;
+}
+
+/* Intern each symbol name into the group's trie (so it must run before
+ build_string_table_section for the same group). */
+std::vector<uint8_t>
+gcov_writer_v4::build_symbol_names_section (size_t group_idx)
+{
+ std::vector<uint8_t> buf;
+ file_group &fg = file_groups[group_idx];
+ write_be8 (
+ buf, v4::SECTION_SYMBOL_NAMES | (compact ? v4::SECTION_COMPACT_FLAG : 0));
+ write_int32 (buf, static_cast<uint32_t> (fg.symbol_indices.size ()),
compact);
+ for (uint32_t name_idx : fg.symbol_indices)
+ {
+ write_int32 (buf,
+ fg.string_trie.intern (
+ profile->get_function_names ()[name_idx]),
+ compact);
+ write_int32 (buf, old_to_new_id[name_idx], compact);
+ auto it = symbol_to_section.find (name_idx);
+ write_int32 (buf,
+ it != symbol_to_section.end () ? it->second : INVALID_INDEX,
+ compact);
+ }
+ return buf;
+}
+
+std::vector<uint8_t>
+gcov_writer_v4::build_string_table_section (size_t group_idx) const
+{
+ std::vector<uint8_t> buf;
+ const file_group &fg = file_groups[group_idx];
+ write_be8 (
+ buf, v4::SECTION_STRING_TABLE | (compact ? v4::SECTION_COMPACT_FLAG : 0));
+ write_int32 (buf, fg.string_trie.next_id, compact);
+ serialize_trie_node (buf, fg.string_trie, 0);
+ return buf;
+}
+
+std::vector<uint8_t>
+gcov_writer_v4::build_symbol_info_section (const gcov_function &func) const
+{
+ std::vector<uint8_t> buf;
+ write_be8 (
+ buf, v4::SECTION_SYMBOL_INFO | (compact ? v4::SECTION_COMPACT_FLAG : 0));
+ write_symbol_info_body (buf, func);
+ return buf;
+}
+
+void
+gcov_writer_v4::collect_cross_group_inliners (
+ const gcov_function &f, uint32_t top_new_id, uint32_t top_group,
+ const std::unordered_map<uint32_t, uint32_t> &group_of,
+ std::vector<std::set<uint32_t>> &inliners) const
+{
+ for (const auto &loc : f.locations)
+ if (loc->type == gcov_location::kind::inlined)
+ {
+ const auto *inl
+ = static_cast<const gcov_location_inline *> (loc.get ());
+ auto git = group_of.find (inl->inlined_sym.id);
+ uint32_t g = git != group_of.end () ? git->second : 0;
+ if (g != top_group)
+ inliners[g].insert (top_new_id);
+ collect_cross_group_inliners (
+ inl->inlined_sym, top_new_id, top_group, group_of, inliners);
+ }
+}
+
+/* Reverse inline index: per file group, the new symbol ids of top-level
+ functions in OTHER groups whose inline tree contains a symbol of this group.
+ Lets a partial read pull exactly the cross-module inliners of a TU. */
+std::vector<std::pair<uint32_t, std::vector<uint32_t>>>
+gcov_writer_v4::compute_inline_groups () const
+{
+ std::unordered_map<uint32_t, uint32_t> group_of;
+ for (uint32_t g = 0; g < file_groups.size (); ++g)
+ for (uint32_t old_idx : file_groups[g].symbol_indices)
+ group_of[old_idx] = g;
+
+ std::vector<std::set<uint32_t>> inliners (file_groups.size ());
+ for (const auto &func : profile->get_functions ())
+ {
+ auto nit = old_to_new_id.find (func.id);
+ auto git = group_of.find (func.id);
+ collect_cross_group_inliners (
+ func,
+ nit != old_to_new_id.end () ? nit->second : INVALID_INDEX,
+ git != group_of.end () ? git->second : 0,
+ group_of,
+ inliners);
+ }
+
+ /* One entry per file that has cross-group inliners (sparse); std::set keeps
+ the ids sorted. */
+ std::vector<std::pair<uint32_t, std::vector<uint32_t>>> groups;
+ for (uint32_t g = 0; g < inliners.size (); ++g)
+ if (!inliners[g].empty ())
+ groups.emplace_back (
+ g, std::vector<uint32_t> (inliners[g].begin (), inliners[g].end ()));
+ return groups;
+}
+
+std::vector<uint8_t>
+gcov_writer_v4::build_inline_index_section (
+ const std::vector<uint32_t> &ids) const
+{
+ std::vector<uint8_t> buf;
+ write_be8 (
+ buf, v4::SECTION_INLINE_INDICES | (compact ? v4::SECTION_COMPACT_FLAG :
0));
+ write_int32 (buf, static_cast<uint32_t> (ids.size ()), compact);
+ for (uint32_t id : ids)
+ write_int32 (buf, id, compact);
+ return buf;
+}
+
+std::vector<uint8_t>
+gcov_writer_v4::build_inline_index_table_section (
+ const std::vector<std::pair<uint32_t, uint32_t>> &entries) const
+{
+ std::vector<uint8_t> buf;
+ write_be8 (
+ buf,
+ v4::SECTION_INLINE_INDEX_TABLE | (compact ? v4::SECTION_COMPACT_FLAG : 0));
+ write_int32 (buf, static_cast<uint32_t> (entries.size ()), compact);
+ for (const auto &entry : entries)
+ {
+ write_int32 (buf, entry.first, compact);
+ write_int32 (buf, entry.second, compact);
+ }
+ return buf;
+}
+
+void
+gcov_writer_v4::write_location_prefix (std::vector<uint8_t> &buf, uint8_t type,
+ uint32_t line, uint32_t disc) const
+{
+ write_be8 (
+ buf, static_cast<uint8_t> (type | (disc ? v4::LOCATION_DISC_FLAG : 0)));
+ write_line_offset (buf, line, compact);
+ if (disc)
+ {
+ if (compact)
+ write_varint (buf, disc);
+ else
+ write_be16 (buf, static_cast<uint16_t> (disc));
+ }
+}
+
+/* A non-inline position: ZERO/NORMAL/WIDE for a plain count, or
+ CALLED_FN/CALLED_FNS when call targets are present (in the call cases the
+ call count stands in for the position count, so the latter is not emitted).
+ */
+void
+gcov_writer_v4::write_position_location (std::vector<uint8_t> &buf,
+ const gcov_location *loc) const
+{
+ const uint32_t line = loc->offset;
+ const uint32_t disc = loc->discriminator & 0xffffu;
+
+ std::vector<std::pair<uint64_t, uint64_t>> targets;
+ if (loc->type == gcov_location::kind::call)
+ {
+ const auto *call = static_cast<const gcov_location_call *> (loc);
+ targets.assign (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;
+ });
+ }
+
+ if (targets.empty ())
+ {
+ const uint64_t count = loc->total_count ();
+ if (count == 0)
+ write_location_prefix (buf, v4::LOCATION_ZERO, line, disc);
+ else if (count <= UINT32_MAX)
+ {
+ write_location_prefix (buf, v4::LOCATION_NORMAL, line, disc);
+ write_int32 (buf, static_cast<uint32_t> (count), compact);
+ }
+ else
+ {
+ write_location_prefix (buf, v4::LOCATION_WIDE, line, disc);
+ write_int (buf, count, compact);
+ }
+ return;
+ }
+
+ auto new_id = [this] (uint64_t old_id) -> uint32_t {
+ auto it = old_to_new_id.find (static_cast<uint32_t> (old_id));
+ return it != old_to_new_id.end () ? it->second : INVALID_INDEX;
+ };
+
+ if (targets.size () == 1)
+ {
+ write_location_prefix (buf, v4::LOCATION_CALLED_FN, line, disc);
+ write_int32 (buf, new_id (targets[0].first), compact);
+ write_int (buf, targets[0].second, compact);
+ }
+ else
+ {
+ write_location_prefix (buf, v4::LOCATION_CALLED_FNS, line, disc);
+ write_int32 (buf, static_cast<uint32_t> (targets.size ()), compact);
+ for (const auto &t : targets)
+ {
+ write_int32 (buf, new_id (t.first), compact);
+ write_int (buf, t.second, compact);
+ }
+ }
+}
+
+/* Body shared by top-level and inlined functions: num_locs, then each position
+ (non-inline) location, then each inlined callsite. */
+static void
+split_locations (const gcov_function &func,
+ std::vector<const gcov_location *> *positions,
+ std::vector<const gcov_location_inline *> *inlines)
+{
+ positions->reserve (positions->size () + func.locations.size ());
+ inlines->reserve (inlines->size () + func.locations.size ());
+ for (const auto &loc : func.locations)
+ {
+ if (loc->type == gcov_location::kind::inlined)
+ inlines->push_back (
+ static_cast<const gcov_location_inline *> (loc.get ()));
+ else
+ positions->push_back (loc.get ());
+ }
+}
+
+void
+gcov_writer_v4::write_inlined_body (std::vector<uint8_t> &buf,
+ const gcov_function &func) const
+{
+ std::vector<const gcov_location *> positions;
+ std::vector<const gcov_location_inline *> inlines;
+ split_locations (func, &positions, &inlines);
+
+ write_int32 (
+ buf, static_cast<uint32_t> (positions.size () + inlines.size ()), compact);
+ for (const gcov_location *loc : positions)
+ write_position_location (buf, loc);
+ for (const gcov_location_inline *cs : inlines)
+ {
+ write_location_prefix (
+ buf, v4::LOCATION_INLINED_FN, cs->offset, cs->discriminator & 0xffffu);
+ auto it = old_to_new_id.find (cs->inlined_sym.id);
+ write_int32 (
+ buf, it != old_to_new_id.end () ? it->second : INVALID_INDEX, compact);
+ write_inlined_body (buf, cs->inlined_sym);
+ }
+}
+
+void
+gcov_writer_v4::write_symbol_info_body (std::vector<uint8_t> &buf,
+ const gcov_function &func) const
+{
+ write_int (buf, func.head_count, compact);
+ write_int (buf, func.timestamp, compact);
+ write_inlined_body (buf, func);
+}
+
+bool
+gcov_writer_v4::write_binary ()
+{
+ build_file_groups ();
+
+ /* Assign global section indices (0=SUMMARY, 1=FILE_NAMES, then per-group
+ STRING_TABLE/SYMBOL_NAMES, then one SYMBOL_INFO per top-level function),
+ contiguous symbol ids per group, and the old->new id map. */
+ uint32_t next_section = 2;
+ uint32_t next_id = 0;
+ for (auto &fg : file_groups)
+ {
+ fg.string_table_section = next_section++;
+ fg.symbol_names_section = next_section++;
+ fg.symbol_id_begin = next_id;
+ for (uint32_t old_idx : fg.symbol_indices)
+ old_to_new_id[old_idx] = next_id++;
+ fg.symbol_id_end = next_id;
+ }
+ for (const auto &func : profile->get_functions ())
+ symbol_to_section[func.id] = next_section++;
+
+ /* Section count is known from the groups/functions up front. With the
+ fixed-width table the header size is too. Stream each section straight to
+ the file (build -> write -> free) and record only a small {offset, size,
+ bitmask} directory, then seek back and write the header. No section bytes
+ are held beyond the one being written. */
+
+ /* One SECTION_INLINE_INDICES sub-section per file with cross-group inliners,
+ plus the SECTION_INLINE_INDEX_TABLE directory. */
+ const std::vector<std::pair<uint32_t, std::vector<uint32_t>>> inline_groups
+ = compute_inline_groups ();
+ const uint64_t total_sections = 2 + file_groups.size () * 2
+ + profile->get_functions ().size ()
+ + inline_groups.size () + 1;
+ const uint64_t header_size = 4 + 4 + 1 + 7 + total_sections * 16;
+
+ std::vector<section_directory_entry> dir;
+ dir.reserve (total_sections);
+
+ if (fseek (file, (long) header_size, SEEK_SET) != 0)
+ return false;
+ bool io_ok = true;
+
+ io_ok &= emit_section (file, build_summary_section (), dir);
+ io_ok &= emit_section (file, build_file_names_section (), dir);
+ for (size_t i = 0; i < file_groups.size (); ++i)
+ {
+ /* Build SYMBOL_NAMES first (interns strings into the trie), then the
+ STRING_TABLE. Emit STRING_TABLE before SYMBOL_NAMES (index order). */
+ std::vector<uint8_t> sym_names = build_symbol_names_section (i);
+ io_ok &= emit_section (file, build_string_table_section (i), dir);
+ io_ok &= emit_section (file, sym_names, dir);
+ }
+ for (const auto &func : profile->get_functions ())
+ io_ok &= emit_section (file, build_symbol_info_section (func), dir);
+ /* Per-file inline-index sub-sections. Record each one's section index, then
+ emit the directory mapping file id -> that index. */
+ std::vector<std::pair<uint32_t, uint32_t>> ii_table;
+ ii_table.reserve (inline_groups.size ());
+ for (const auto &group : inline_groups)
+ {
+ ii_table.emplace_back (group.first, static_cast<uint32_t> (dir.size ()));
+ io_ok
+ &= emit_section (file, build_inline_index_section (group.second), dir);
+ }
+ io_ok
+ &= emit_section (file, build_inline_index_table_section (ii_table), dir);
+
+ /* Header: magic, version, reserved byte, section count, then a fixed-width
+ 16-byte entry per section { offset(8), size(7), bitmask(1) }. */
+ std::vector<uint8_t> header;
+ header.push_back ('g');
+ header.push_back ('c');
+ header.push_back ('o');
+ header.push_back ('v');
+ write_be32 (header, v4::format::AFDO_GCOV_VERSION);
+ write_be8 (header, 0); /* Reserved, must be 0. */
+ v4::write_be56 (header, total_sections);
+ for (const section_directory_entry &e : dir)
+ {
+ v4::write_be64 (header, e.offset);
+ v4::write_be56 (header, e.size);
+ write_be8 (header, e.bitmask);
+ }
+
+ if (!io_ok || fseek (file, 0, SEEK_SET) != 0)
+ return false;
+ fwrite (header.data (), 1, header.size (), file);
+
+ return file && !ferror (file);
+}
+
+bool
+gcov_writer_v4::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;
+ }
+ return is_text ? write_text () : write_binary ();
+}
+
+int32_t
+gcov_writer_v4::file_id_for (uint32_t id) const
+{
+ if (id < profile->get_file_name_map ().size ())
+ {
+ const uint32_t idx = profile->get_file_name_map ()[id];
+ if (idx < profile->get_file_names ().size ())
+ return static_cast<int32_t> (idx);
+ }
+ return -1;
+}
+
+/* Emit FUNC's locations/callsites/inlined sub-sections (text format), each
+ indented by INDENT levels. INLINED nodes recurse through
+ write_text_inlined. */
+void
+gcov_writer_v4::write_text_sections (FILE *out, const gcov_function &func,
+ int indent) const
+{
+ const std::string pad (static_cast<size_t> (indent) * 2, ' ');
+
+ std::vector<const gcov_location *> locs, calls;
+ std::vector<const gcov_location_inline *> inls;
+ for (const auto &loc : func.locations)
+ {
+ if (loc->type == gcov_location::kind::inlined)
+ inls.push_back (static_cast<const gcov_location_inline *> (loc.get ()));
+ else if (loc->type == gcov_location::kind::call
+ && !static_cast<const gcov_location_call *> (loc.get ())
+ ->callee_map.empty ())
+ calls.push_back (loc.get ());
+ else
+ locs.push_back (loc.get ());
+ }
+
+ bool need_comma = false;
+ if (!locs.empty ())
+ {
+ fprintf (out, "%slocations = {\n", pad.c_str ());
+ for (size_t i = 0; i < locs.size (); ++i)
+ {
+ if (i)
+ fprintf (out, ",\n");
+ fprintf (out,
+ "%s %s = %" PRIu64,
+ pad.c_str (),
+ fmt_loc (locs[i]).c_str (),
+ locs[i]->total_count ());
+ }
+ fprintf (out, "\n%s}", pad.c_str ());
+ need_comma = true;
+ }
+ if (!calls.empty ())
+ {
+ if (need_comma)
+ fprintf (out, ",\n");
+ fprintf (out, "%scallsites = {\n", pad.c_str ());
+ for (size_t i = 0; i < calls.size (); ++i)
+ {
+ if (i)
+ fprintf (out, ",\n");
+ const auto *call = static_cast<const gcov_location_call *> (calls[i]);
+ fprintf (
+ out, "%s %s -> {", pad.c_str (), fmt_loc (calls[i]).c_str ());
+ std::vector<std::pair<uint64_t, uint64_t>> tg (
+ call->callee_map.begin (), call->callee_map.end ());
+ std::sort (tg.begin (), tg.end ());
+ for (size_t t = 0; t < tg.size (); ++t)
+ {
+ if (t)
+ fprintf (out, ",");
+ /* "name":id = count. The name keeps ICP targets (callee-only
+ symbols with no header of their own) recoverable from text. */
+ const char *tname
+ = tg[t].first < profile->get_function_names ().size ()
+ ? profile->get_function_names ()[tg[t].first].c_str ()
+ : "";
+ fprintf (out,
+ " \"%s\":%" PRIu64 " = %" PRIu64,
+ tname,
+ tg[t].first,
+ tg[t].second);
+ }
+ fprintf (out, " }");
+ }
+ fprintf (out, "\n%s}", pad.c_str ());
+ need_comma = true;
+ }
+ if (!inls.empty ())
+ {
+ if (need_comma)
+ fprintf (out, ",\n");
+ fprintf (out, "%sinlined = {\n", pad.c_str ());
+ for (size_t i = 0; i < inls.size (); ++i)
+ {
+ if (i)
+ fprintf (out, ",\n");
+ write_text_inlined (out, inls[i], indent);
+ }
+ fprintf (out, "\n%s}", pad.c_str ());
+ need_comma = true;
+ }
+ if (need_comma)
+ fprintf (out, "\n");
+}
+
+/* Emit one inlined callsite CS (text format) at INDENT. INLINED nodes carry
+ only (id:total) plus their own nested sub-sections. */
+void
+gcov_writer_v4::write_text_inlined (FILE *out, const gcov_location_inline *cs,
+ int indent) const
+{
+ const gcov_function &nested = cs->inlined_sym;
+ const std::string pad (static_cast<size_t> (indent + 1) * 2, ' ');
+ const uint64_t total = text_position_count (nested);
+ const std::vector<std::string> &names = profile->get_function_names ();
+ const char *nm
+ = nested.desc.name_index < names.size ()
+ ? names[nested.desc.name_index].c_str ()
+ : "";
+ fprintf (out,
+ "%s%s = \"%s\":%d(%u:%" PRIu64 ") = {\n",
+ pad.c_str (),
+ fmt_loc (cs).c_str (),
+ nm,
+ file_id_for (nested.id),
+ (unsigned) nested.id,
+ total);
+ write_text_sections (out, nested, indent + 2);
+ fprintf (out, "%s}", pad.c_str ());
+}
+
+/* Human-readable v4 text format. Unlike the binary format, it keys symbols by
+ their name-pool index (no per-TU grouping / contiguous ids). */
+bool
+gcov_writer_v4::write_text ()
+{
+ FILE *out = file;
+
+ fprintf (out, "filenames = {\n");
+ for (size_t i = 0; i < profile->get_file_names ().size (); ++i)
+ fprintf (out,
+ " \"%s\"%s\n",
+ profile->get_file_names ()[i].c_str (),
+ (i + 1 < profile->get_file_names ().size () ? "," : ""));
+ fprintf (out, "}\n");
+
+ const gcov_summary &s = profile->get_summary ();
+ fprintf (out, "\nsummary = {\n");
+ fprintf (out, " total_count = %" PRIu64 ",\n", s.total_count);
+ fprintf (out, " max_count = %" PRIu64 ",\n", s.max_count);
+ fprintf (out, " max_fn_count = %" PRIu64 ",\n", s.max_function_count);
+ fprintf (out, " num_counts = %" PRIu64 ",\n", s.num_counts);
+ fprintf (out, " num_functions = %" PRIu64 ",\n", s.num_functions);
+ fprintf (out, " num_detailed_entries = %" PRIu64 ",\n",
+ s.num_detailed_entries);
+ fprintf (out, " detailed_entries = {\n");
+ for (size_t i = 0; i < s.detailed_entries.size (); ++i)
+ {
+ const auto &e = s.detailed_entries[i];
+ fprintf (out,
+ " {cutoff = %u, min_count = %" PRIu64
+ ", num_counts = %" PRIu64 "}%s\n",
+ (unsigned) e.cutoff,
+ e.min_count,
+ e.num_counts,
+ (i + 1 < s.detailed_entries.size () ? "," : ""));
+ }
+ fprintf (out, " }\n");
+ fprintf (out, "}\n");
+
+ for (const auto &func : profile->get_functions ())
+ {
+ fprintf (out,
+ "\n\"%s\":%d(%u:%" PRIu64 ":%" PRIu64 ":%" PRIu64 ") = {\n",
+ profile->get_function_names ()[func.desc.name_index].c_str (),
+ file_id_for (func.id),
+ (unsigned) func.id,
+ text_position_count (func),
+ func.head_count,
+ func.timestamp);
+ write_text_sections (out, func, 1);
+ fprintf (out, "}\n");
+ }
+
+ return out && !ferror (out);
+}
+
+} /* namespace gcov. */
--
2.34.1