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

Reply via email to