Define the DWARF types, correlation session, and public reader
interface. The API documents the split between ELF symbol ownership and
DWARF source attribution before adding the implementation.
afdo_tools/ChangeLog:
* src/dwarf_reader.h: New file.
* src/dwarf_types.cc: New file.
* src/dwarf_types.h: New file.
Signed-off-by: Kugan Vivekanandarajah <[email protected]>
Co-authored-by: Dhruv Chawla <[email protected]>
Co-authored-by: Rahul Shinde <[email protected]>
---
afdo_tools/src/dwarf_reader.h | 167 ++++++++++++++++++++++++++++++++++
afdo_tools/src/dwarf_types.cc | 82 +++++++++++++++++
afdo_tools/src/dwarf_types.h | 141 ++++++++++++++++++++++++++++
3 files changed, 390 insertions(+)
create mode 100644 afdo_tools/src/dwarf_reader.h
create mode 100644 afdo_tools/src/dwarf_types.cc
create mode 100644 afdo_tools/src/dwarf_types.h
diff --git a/afdo_tools/src/dwarf_reader.h b/afdo_tools/src/dwarf_reader.h
new file mode 100644
index 00000000000..19cf7cbbd64
--- /dev/null
+++ b/afdo_tools/src/dwarf_reader.h
@@ -0,0 +1,167 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_DWARF_READER_H
+#define GCC_DWARF_READER_H
+
+#include "dwarf_types.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace dwarf_debug {
+
+class dwarf_reader_impl;
+
+struct correlate_symbolize_entry
+{
+ interned_string function_name = nullptr;
+ /* A PC may have a primary location and multiple exact-PC is_stmt rows. */
+ std::vector<source_location> locations;
+ /* Index in LOCATIONS of the addr2line-style row; locations[0] is the
+ deposit row. */
+ size_t dwarf_line_index = 0;
+ /* Reader-owned and valid for the dwarf_reader lifetime. */
+ const std::vector<inline_frame> *inline_stack = nullptr;
+};
+
+/* Cached symbolization, including all candidate locations, by address. */
+using correlate_symbolize_map
+ = std::unordered_map<uint64_t, correlate_symbolize_entry>;
+
+/* Per-worker cache for dwarf_reader::symbolize. */
+class correlate_session
+{
+public:
+ correlate_session () = default;
+ ~correlate_session () = default;
+
+ correlate_session (correlate_session &&) noexcept = default;
+ correlate_session &operator= (correlate_session &&) noexcept = default;
+
+ correlate_session (const correlate_session &) = delete;
+ correlate_session &operator= (const correlate_session &) = delete;
+
+ /* Move this session's cached symbolizations into DEST. */
+ void drain_symbolize_snapshot_into (correlate_symbolize_map &dest);
+
+private:
+ friend class dwarf_reader_impl;
+ correlate_symbolize_map symbolize_cache_;
+};
+
+/* Sample-scoped DWARF + ELF symtab index: containing function, locations,
+ inline stacks, and decl identity.
+
+ Lifecycle: set_parse_threads(), load(), then concurrent const queries.
+ Returned string/stack views are reader-owned until destroy. One
+ correlate_session per worker thread.
+
+ ELF and DWARF answer different questions, and the profile needs both.
+ The ELF symtab says which function owns an address:
+ it covers every STT_FUNC in the binary, including ones compiled without
+ debug info, and its [st_value, st_value + st_size) spans are what samples
+ are bucketed into. DWARF says where an address came from in the source:
+ file, line, discriminator, and the inline frames it was inlined through.
+ So the symtab gives the profile its function keys and DWARF fills in the
+ line-level bodies underneath them.
+
+ A profile usually touches only a small part of a release binary's DWARF,
+ so the reader prefers not to parse the whole thing. The constructor
+ takes the sampled [start, end) virtual-address ranges. When
+ .debug_aranges is present, it is scanned first to find compilation units
+ that overlap those ranges, and only those CUs get their line tables and
+ DIE trees parsed. If .debug_aranges is absent, empty, or has no entry
+ overlapping a sampled range, the reader scans every compilation unit
+ instead.
+ Parse is in parallel, one libdw handle per worker because libdw handles
+ are not thread-safe. The exception is the whole-binary DIE walk, which
+ has to cross CU boundaries; see the getscopes note below.
+
+ Address to symbol. Each .debug_line row starts a [start, end) span
+ carrying one (file, line, discriminator). The spans of the sampled CUs
+ are flattened into one address-sorted table with a segment-tree index
+ over it, since spans nest and overlap and a plain sorted lookup cannot
+ find every covering span. Symbolizing a PC queries that index and keeps
+ the tightest covering span, preferring a row whose line-table address is
+ exactly the PC. Inlining is indexed separately: each inlined_subroutine
+ DIE contributes its PC ranges and the frame stack above it, so a PC also
+ resolves to the chain of callsites it was inlined through.
+
+ A PC can land in a hole, for instance inside a DW_AT_ranges gap, and then
+ no span covers it. The reader falls back to the declaring file and line
+ of the subprogram that owns the address. Finding that from a DIE alone
+ would mean asking libdw
+ for the enclosing scope chain, and dwarf_getscopes_die walks the DIE tree
+ upward on every call, which dominated load time on large binaries. The
+ walk is instead done once: a whole-binary DIE pass records each
+ subprogram's enclosing class, struct or union declaration site up front,
+ so the common case is a hash lookup rather than a tree walk. A DIE the
+ pass did not index still falls through to dwarf_getscopes_die, but that
+ is now the last resort rather than the rule.
+
+ That same whole-binary pass also resolves ICF aliases. When identical
+ code folding merges two functions, the alias can be left with no
+ code-bearing DIE of its own, so the only record of its declaring file is
+ a name-only definition that may sit in a different CU than the code. The
+ pass collects those by name, which is why it cannot be limited to the
+ sampled CUs. */
+class dwarf_reader
+{
+public:
+ explicit dwarf_reader (std::string binary_path, pc_range_list
sampled_ranges);
+ ~dwarf_reader ();
+
+ dwarf_reader (const dwarf_reader &) = delete;
+ dwarf_reader &operator= (const dwarf_reader &) = delete;
+
+ void set_parse_threads (unsigned n);
+ bool load ();
+ const dwarf_load_summary &load_summary () const;
+
+ const std::vector<function_metadata> &symtab_functions () const;
+ const std::vector<function_alias_metadata> &symtab_aliases () const;
+ /* Symtab function whose [start,end) contains ADDRESS. */
+ const function_metadata *containing_function_at_pc (uint64_t address) const;
+
+ symbolized_result
+ symbolize (uint64_t file_offset, correlate_session &session,
+ const function_metadata &containing_function) const;
+
+ /* One PC per distinct line-table span in [start, end). Symbolizing these
+ completes a function's callsite tree, which sampled PCs alone would leave
+ missing the inlined callsites that took no samples. OUT is reused across
+ calls. */
+ void collect_line_span_seed_pcs (uint64_t start, uint64_t end,
+ std::vector<uint64_t> &out) const;
+
+ source_location
+ resolve_inline_frame_decl_location (const inline_frame &frame,
+ const std::string &caller_file) const;
+
+private:
+ std::unique_ptr<dwarf_reader_impl> impl_;
+};
+
+} /* namespace dwarf_debug. */
+
+#endif /* GCC_DWARF_READER_H. */
diff --git a/afdo_tools/src/dwarf_types.cc b/afdo_tools/src/dwarf_types.cc
new file mode 100644
index 00000000000..ea7033e1605
--- /dev/null
+++ b/afdo_tools/src/dwarf_types.cc
@@ -0,0 +1,82 @@
+/* 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 "dwarf_types.h"
+
+namespace dwarf_debug {
+
+const std::string &
+string_value (interned_string value)
+{
+ static const std::string empty;
+ return value == nullptr ? empty : *value;
+}
+
+bool
+string_empty (interned_string value)
+{
+ return value == nullptr || value->empty ();
+}
+
+const std::vector<inline_frame> &
+inline_stack_or_empty (const std::vector<inline_frame> *stack)
+{
+ static const std::vector<inline_frame> empty;
+ return stack != nullptr ? *stack : empty;
+}
+
+bool
+inline_stacks_equal (const std::vector<inline_frame> &a,
+ const std::vector<inline_frame> &b)
+{
+ if (a.size () != b.size ())
+ return false;
+ for (size_t i = 0; i < a.size (); ++i)
+ {
+ const inline_frame &fa = a[i];
+ const inline_frame &fb = b[i];
+ if (fa.function_name != fb.function_name || fa.file_path != fb.file_path
+ || fa.caller_file_path != fb.caller_file_path
+ || fa.line_number != fb.line_number || fa.call_line != fb.call_line
+ || fa.discriminator != fb.discriminator)
+ return false;
+ }
+ return true;
+}
+
+bool
+source_location_equal (const source_location &a, const source_location &b)
+{
+ return (a.file_path == b.file_path
+ && a.line_number == b.line_number
+ && a.discriminator == b.discriminator);
+}
+
+bool
+source_locations_equal (const std::vector<source_location> &a,
+ const std::vector<source_location> &b)
+{
+ if (a.size () != b.size ())
+ return false;
+ for (size_t i = 0; i < a.size (); ++i)
+ if (!source_location_equal (a[i], b[i]))
+ return false;
+ return true;
+}
+
+} /* namespace dwarf_debug. */
diff --git a/afdo_tools/src/dwarf_types.h b/afdo_tools/src/dwarf_types.h
new file mode 100644
index 00000000000..d2e4cbf709c
--- /dev/null
+++ b/afdo_tools/src/dwarf_types.h
@@ -0,0 +1,141 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_DWARF_TYPES_H
+#define GCC_DWARF_TYPES_H
+
+#include "hash_util.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace dwarf_debug {
+
+using interned_string = const std::string *;
+
+/* A list of half-open [start, end) address ranges. */
+using pc_range_list = std::vector<std::pair<uint64_t, uint64_t>>;
+
+/* Empty interned values are represented by nullptr. */
+const std::string &string_value (interned_string);
+bool string_empty (interned_string);
+
+struct function_instance_key
+{
+ interned_string name = nullptr;
+ uint64_t start_addr = 0;
+
+ bool operator== (const function_instance_key &o) const noexcept
+ {
+ return start_addr == o.start_addr && name == o.name;
+ }
+};
+
+struct function_instance_key_hash
+{
+ size_t operator() (const function_instance_key &key) const noexcept
+ {
+ size_t hash = std::hash<interned_string>{}(key.name);
+ autofdo::hash_combine (hash, std::hash<uint64_t>{}(key.start_addr));
+ return hash;
+ }
+};
+
+/* ELF symbol range with DWARF export anchors. */
+struct function_metadata
+{
+ interned_string name = nullptr;
+ interned_string file_path = nullptr;
+ uint64_t start_addr = 0;
+ uint64_t end_addr = 0;
+ uint32_t start_line = 0;
+
+ function_metadata () = default;
+ function_metadata (interned_string n, interned_string file, uint64_t start,
+ uint64_t end, uint32_t sl = 0)
+ : name (n), file_path (file), start_addr (start), end_addr (end),
+ start_line (sl)
+ {}
+};
+
+struct function_alias_metadata
+{
+ function_metadata function;
+ size_t primary_index = 0;
+};
+
+struct inline_frame
+{
+ interned_string function_name = nullptr;
+ interned_string file_path = nullptr;
+ interned_string caller_file_path = nullptr;
+ uint32_t line_number = 0;
+ uint32_t call_line = 0;
+ uint32_t discriminator = 0;
+};
+
+struct source_location
+{
+ interned_string file_path = nullptr;
+ uint32_t line_number = 0;
+ uint32_t discriminator = 0;
+};
+
+struct symbolized_result
+{
+ const function_metadata *function = nullptr;
+ const std::vector<source_location> *locations = nullptr;
+ /* Inline stack (reader-owned when set). */
+ const std::vector<inline_frame> *inline_stack = nullptr;
+};
+
+struct dwarf_load_summary
+{
+ size_t touch_cus = 0;
+ size_t dwarf_functions = 0;
+ size_t symtab_functions = 0;
+ size_t line_segments = 0;
+ /* Unique profile-touch PCs (range endpoints); not a symbolize cache size.
*/
+ size_t touch_pcs = 0;
+};
+
+/* STACK, or a shared empty one when the PC carries no inline frames, so
+ callers can bind a reference without testing for null. */
+const std::vector<inline_frame> &
+inline_stack_or_empty (const std::vector<inline_frame> *);
+
+/* True when A and B describe the same inline frame at every depth. */
+bool
+inline_stacks_equal (const std::vector<inline_frame> &a,
+ const std::vector<inline_frame> &b);
+
+/* True when A and B name the same file, line and discriminator. */
+bool source_location_equal (const source_location &, const source_location &);
+
+/* True when A and B agree at every position. */
+bool
+source_locations_equal (const std::vector<source_location> &a,
+ const std::vector<source_location> &b);
+
+} /* namespace dwarf_debug. */
+
+#endif /* GCC_DWARF_TYPES_H. */
--
2.34.1