Add a self-contained decoder for ARM Statistical Profiling Extension
auxiliary packets. It reconstructs packet state and reports sampled
addresses, events, contexts, and operation metadata to the SPE reader.
afdo_tools/ChangeLog:
* src/arm_spe_aux_decode.cc: New file.
* src/arm_spe_aux_decode.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/arm_spe_aux_decode.cc | 531 +++++++++++++++++++++++++++
afdo_tools/src/arm_spe_aux_decode.h | 100 +++++
2 files changed, 631 insertions(+)
create mode 100644 afdo_tools/src/arm_spe_aux_decode.cc
create mode 100644 afdo_tools/src/arm_spe_aux_decode.h
diff --git a/afdo_tools/src/arm_spe_aux_decode.cc
b/afdo_tools/src/arm_spe_aux_decode.cc
new file mode 100644
index 00000000000..04bd1b96947
--- /dev/null
+++ b/afdo_tools/src/arm_spe_aux_decode.cc
@@ -0,0 +1,531 @@
+/* Minimal ARM SPE (Statistical Profiling Extension) auxtrace decoder.
+ Packet layout follows tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c
+ (Linux v6.12). */
+
+/* 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 "arm_spe_aux_decode.h"
+
+#include <algorithm>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <fcntl.h>
+#include <gelf.h>
+#include <libelf.h>
+#include <unistd.h>
+
+namespace perf_data {
+
+namespace {
+
+/* Contiguous bit mask covering bits H down to L, as in Linux GENMASK_ULL. */
+constexpr uint64_t
+genmask_ull (unsigned h, unsigned l)
+{
+ return ((~0ULL) >> (63 - h + l)) << l;
+}
+
+/* Packet headers (Linux arm-spe-pkt-decoder.h). */
+constexpr unsigned SPE_HEADER0_PAD = 0x0;
+constexpr unsigned SPE_HEADER0_END = 0x1;
+constexpr unsigned SPE_HEADER0_TIMESTAMP = 0x71;
+constexpr unsigned SPE_HEADER0_EVENTS = 0x42;
+constexpr unsigned SPE_HEADER0_SOURCE = 0x43;
+constexpr unsigned SPE_HEADER0_CONTEXT = 0x64;
+constexpr unsigned SPE_HEADER0_OP_TYPE = 0x48;
+constexpr unsigned SPE_HEADER0_EXTENDED = 0x20;
+constexpr unsigned SPE_HEADER0_ADDRESS = 0xb0;
+constexpr unsigned SPE_HEADER0_COUNTER = 0x98;
+constexpr unsigned SPE_HEADER1_ALIGNMENT = 0x0;
+
+constexpr uint64_t SPE_HEADER0_MASK1 = genmask_ull (7, 6) | genmask_ull (3, 0);
+constexpr uint64_t SPE_HEADER0_MASK2 = genmask_ull (7, 2);
+constexpr uint64_t SPE_HEADER0_MASK3 = genmask_ull (7, 3);
+
+constexpr unsigned SPE_ADDR_PKT_HDR_INDEX_INS = 0x0;
+constexpr unsigned SPE_ADDR_PKT_HDR_INDEX_BRANCH = 0x1;
+
+constexpr unsigned SPE_OP_PKT_HDR_CLASS_BR_ERET = 0x2;
+constexpr uint64_t SPE_OP_PKT_COND = 0x1;
+
+constexpr unsigned EV_RETIRED = 1;
+constexpr unsigned EV_NOT_TAKEN = 6;
+
+constexpr unsigned SPE_ADDR_PKT_ADDR_BYTE7_SHIFT = 56;
+constexpr unsigned SPE_ADDR_PKT_EL1 = 1;
+constexpr unsigned SPE_ADDR_PKT_EL2 = 2;
+
+constexpr int SPE_NEED_MORE = -1;
+constexpr int SPE_BAD = -2;
+
+constexpr uint64_t MAX_SPE_RANGE_BYTES = 0x1000;
+
+enum spe_pkt_type
+{
+ SPE_PAD,
+ SPE_END,
+ SPE_TS,
+ SPE_ADDR,
+ SPE_COUNTER,
+ SPE_CTX,
+ SPE_OP,
+ SPE_EVENTS,
+ SPE_DATA_SRC,
+};
+
+struct spe_pkt
+{
+ spe_pkt_type type = SPE_PAD;
+ unsigned char index = 0;
+ uint64_t payload = 0;
+};
+
+static unsigned
+spe_hdr_short_index (unsigned h)
+{
+ return h & genmask_ull (2, 0);
+}
+
+static unsigned
+spe_hdr_extended_index (unsigned h0, unsigned h1)
+{
+ return ((h0 & genmask_ull (1, 0)) << 3) | spe_hdr_short_index (h1);
+}
+
+static unsigned
+spe_op_pkt_hdr_class (unsigned h)
+{
+ return h & genmask_ull (1, 0);
+}
+
+/* Payload byte count encoded in bits 5:4 of a packet header. */
+static unsigned
+spe_payload_len (unsigned char hdr)
+{
+ return 1U << ((hdr & genmask_ull (5, 4)) >> 4);
+}
+
+/* Mark PACKET as TYPE and read the payload following its 1 + EXT_HDR header
+ bytes. Returns the whole packet length, or SPE_NEED_MORE / SPE_BAD. */
+static int
+spe_get_payload (const uint8_t *buf, size_t len, unsigned char ext_hdr,
+ spe_pkt_type type, spe_pkt *packet)
+{
+ packet->type = type;
+ const size_t payload_len = spe_payload_len (buf[ext_hdr]);
+ if (len < 1u + ext_hdr + payload_len)
+ return SPE_NEED_MORE;
+ buf += 1 + ext_hdr;
+ switch (payload_len)
+ {
+ case 1:
+ packet->payload = *buf;
+ break;
+ case 2:
+ packet->payload = read_u16 (buf);
+ break;
+ case 4:
+ packet->payload = read_u32 (buf);
+ break;
+ case 8:
+ packet->payload = read_u64 (buf);
+ break;
+ default:
+ return SPE_BAD;
+ }
+ return static_cast<int> (1 + ext_hdr + payload_len);
+}
+
+/* Consume an alignment PAD up to the next boundary in the full stream. */
+static int
+spe_get_alignment (const uint8_t *buf, size_t len, uint64_t stream_offset,
+ spe_pkt *packet)
+{
+ const unsigned alignment = 1u << ((buf[0] & 0xf) + 1);
+ if (len < alignment)
+ return SPE_NEED_MORE;
+ packet->type = SPE_PAD;
+ const unsigned mis
+ = static_cast<unsigned> (stream_offset & (alignment - 1));
+ return static_cast<int> (alignment - mis);
+}
+
+/* Address packets carry the address kind in their header index field. */
+static int
+spe_get_addr (const uint8_t *buf, size_t len, unsigned char ext_hdr,
+ spe_pkt *packet)
+{
+ if (ext_hdr)
+ packet->index
+ = static_cast<unsigned char> (spe_hdr_extended_index (buf[0], buf[1]));
+ else
+ packet->index = static_cast<unsigned char> (spe_hdr_short_index (buf[0]));
+ return spe_get_payload (buf, len, ext_hdr, SPE_ADDR, packet);
+}
+
+/* Decode the packet at BUF into PACKET and return its length in bytes. */
+static int
+spe_do_get_packet (const uint8_t *buf, size_t len, uint64_t stream_offset,
+ spe_pkt *packet)
+{
+ unsigned char ext_hdr = 0;
+
+ *packet = spe_pkt{};
+ if (!len)
+ return SPE_NEED_MORE;
+
+ unsigned hdr = buf[0];
+
+ if (hdr == SPE_HEADER0_PAD)
+ {
+ packet->type = SPE_PAD;
+ return 1;
+ }
+ if (hdr == SPE_HEADER0_END)
+ {
+ packet->type = SPE_END;
+ return 1;
+ }
+ if (hdr == SPE_HEADER0_TIMESTAMP)
+ return spe_get_payload (buf, len, 0, SPE_TS, packet);
+ if ((hdr & SPE_HEADER0_MASK1) == SPE_HEADER0_EVENTS)
+ return spe_get_payload (buf, len, 0, SPE_EVENTS, packet);
+ if ((hdr & SPE_HEADER0_MASK1) == SPE_HEADER0_SOURCE)
+ return spe_get_payload (buf, len, 0, SPE_DATA_SRC, packet);
+ if ((hdr & SPE_HEADER0_MASK2) == SPE_HEADER0_CONTEXT)
+ return spe_get_payload (buf, len, 0, SPE_CTX, packet);
+ if ((hdr & SPE_HEADER0_MASK2) == SPE_HEADER0_OP_TYPE)
+ {
+ packet->index = static_cast<unsigned char> (buf[0] & genmask_ull (1, 0));
+ return spe_get_payload (buf, len, 0, SPE_OP, packet);
+ }
+ if ((hdr & SPE_HEADER0_MASK2) == SPE_HEADER0_EXTENDED)
+ {
+ if (len == 1)
+ return SPE_NEED_MORE;
+ ext_hdr = 1;
+ hdr = buf[1];
+ if (hdr == SPE_HEADER1_ALIGNMENT)
+ return spe_get_alignment (buf, len, stream_offset, packet);
+ }
+
+ if ((hdr & SPE_HEADER0_MASK3) == SPE_HEADER0_ADDRESS)
+ return spe_get_addr (buf, len, ext_hdr, packet);
+ if ((hdr & SPE_HEADER0_MASK3) == SPE_HEADER0_COUNTER)
+ return spe_get_payload (buf, len, ext_hdr, SPE_COUNTER, packet);
+
+ return SPE_BAD;
+}
+
+/* As spe_do_get_packet, but coalescing a run of PAD bytes into one step. */
+static int
+spe_get_packet (const uint8_t *buf, size_t len, uint64_t stream_offset,
+ spe_pkt *packet)
+{
+ const int ret = spe_do_get_packet (buf, len, stream_offset, packet);
+ if (ret > 0 && packet->type == SPE_PAD)
+ {
+ /* Collapse short PAD runs (perf collapses up to 16). */
+ int r = ret;
+ while (r < 16 && len > static_cast<size_t> (r) && !buf[r])
+ ++r;
+ return r;
+ }
+ return ret;
+}
+
+/* Virtual address held in an instruction or branch address packet PAYLOAD;
+ non-secure EL1/EL2 addresses carry an implicit all-ones top byte. */
+static uint64_t
+spe_calc_ip (uint64_t payload)
+{
+ const uint64_t ns = (payload >> 63) & 1ULL;
+ const uint64_t el = (payload & genmask_ull (62, 61)) >> 61;
+ uint64_t ip = payload & genmask_ull (55, 0);
+ if (ns && (el == SPE_ADDR_PKT_EL1 || el == SPE_ADDR_PKT_EL2))
+ ip |= 0xffULL << SPE_ADDR_PKT_ADDR_BYTE7_SHIFT;
+ return ip;
+}
+
+} /* namespace. */
+
+void
+arm_spe_stream_decoder::clear_record ()
+{
+ from_ip_ = 0;
+ to_ip_ = 0;
+ events_ = 0;
+ op_payload_ = 0;
+ timestamp_ = 0;
+ op_index_ = 0;
+ have_op_ = false;
+}
+
+/* Account a completed record: a retired branch contributes either a taken
+ edge or, for an untaken conditional branch, a not-taken PC. */
+void
+arm_spe_stream_decoder::commit_record (arm_spe_profile_accumulator *acc)
+{
+ if (!(events_ & (1ULL << EV_RETIRED)) || !have_op_
+ || spe_op_pkt_hdr_class (op_index_) != SPE_OP_PKT_HDR_CLASS_BR_ERET)
+ return;
+ if (from_ip_ == 0 || !acc->accept_addr (from_ip_, acc->ctx))
+ return;
+ if (timestamp_ != 0 && acc->note_timestamp != nullptr)
+ acc->note_timestamp (from_ip_, timestamp_, acc->ctx);
+
+ const bool br_cond = (op_payload_ & SPE_OP_PKT_COND) != 0;
+ const bool not_taken = (events_ & (1ULL << EV_NOT_TAKEN)) != 0;
+ if (br_cond && not_taken)
+ {
+ (*acc->not_taken_counts)[from_ip_] += 1;
+ return;
+ }
+
+ if (to_ip_ == 0 || !acc->accept_addr (to_ip_, acc->ctx))
+ return;
+ if (timestamp_ != 0 && acc->note_timestamp != nullptr)
+ acc->note_timestamp (to_ip_, timestamp_, acc->ctx);
+ (*acc->branch_counts)[{from_ip_, to_ip_}] += 1;
+}
+
+bool
+arm_spe_stream_decoder::consume (const uint8_t *spe_buf, size_t spe_len,
+ arm_spe_profile_accumulator *acc)
+{
+ if ((spe_buf == nullptr && spe_len != 0) || acc == nullptr
+ || acc->accept_addr == nullptr || acc->branch_counts == nullptr
+ || acc->not_taken_counts == nullptr
+ || spe_len > pending_.max_size () - pending_.size ())
+ return false;
+
+ if (spe_len != 0)
+ pending_.insert (pending_.end (), spe_buf, spe_buf + spe_len);
+ size_t pos = 0;
+ while (pos < pending_.size ())
+ {
+ spe_pkt pkt;
+ const int n = spe_get_packet (pending_.data () + pos,
+ pending_.size () - pos,
+ stream_offset_ + pos, &pkt);
+ if (n == SPE_NEED_MORE)
+ break;
+ if (n <= 0)
+ return false;
+
+ switch (pkt.type)
+ {
+ case SPE_END:
+ commit_record (acc);
+ clear_record ();
+ break;
+ case SPE_TS:
+ timestamp_ = pkt.payload;
+ commit_record (acc);
+ clear_record ();
+ break;
+ case SPE_ADDR:
+ if (pkt.index == SPE_ADDR_PKT_HDR_INDEX_INS)
+ from_ip_ = spe_calc_ip (pkt.payload);
+ else if (pkt.index == SPE_ADDR_PKT_HDR_INDEX_BRANCH)
+ to_ip_ = spe_calc_ip (pkt.payload);
+ break;
+ case SPE_EVENTS:
+ events_ = pkt.payload;
+ break;
+ case SPE_OP:
+ op_index_ = pkt.index;
+ op_payload_ = pkt.payload;
+ have_op_ = true;
+ break;
+ default:
+ break;
+ }
+ pos += static_cast<size_t> (n);
+ }
+
+ if (pos != 0)
+ {
+ if (pos > UINT64_MAX - stream_offset_)
+ return false;
+ stream_offset_ += pos;
+ pending_.erase (pending_.begin (), pending_.begin () + pos);
+ }
+ return true;
+}
+
+/* AArch64 insns that never fall through: B, BR/RET/ERET/DRPS (not BL/BLR),
+ BRK/HLT, UDF. Conditional branches still fall through when not taken. */
+
+static bool
+aarch64_insn_falls_through (uint32_t insn)
+{
+ if ((insn >> 26) == 0x05) /* B <label>. */
+ return false;
+ if ((insn >> 25) == 0x6b) /* Branch to register. */
+ return ((insn >> 21) & 0xf) == 0x1; /* BLR falls
through. */
+ if ((insn >> 21) == 0x6a1 || (insn >> 21) == 0x6a2) /* BRK, HLT. */
+ return false;
+ if ((insn >> 16) == 0) /* UDF. */
+ return false;
+ return true;
+}
+
+/* Cache the executable PT_LOAD segments of BINARY_PATH. */
+bool
+arm_text_image::load (const std::string &binary_path)
+{
+ segments_.clear ();
+ if (elf_version (EV_CURRENT) == EV_NONE)
+ return false;
+
+ const int fd = open (binary_path.c_str (), O_RDONLY);
+ if (fd < 0)
+ return false;
+
+ Elf *const elf = elf_begin (fd, ELF_C_READ, nullptr);
+ if (elf == nullptr)
+ {
+ close (fd);
+ return false;
+ }
+
+ size_t phnum = 0;
+ size_t raw_size = 0;
+ const char *const raw = elf_rawfile (elf, &raw_size);
+ if (elf_kind (elf) == ELF_K_ELF && raw != nullptr
+ && elf_getphdrnum (elf, &phnum) == 0)
+ for (size_t i = 0; i < phnum; ++i)
+ {
+ GElf_Phdr phdr;
+ if (gelf_getphdr (elf, i, &phdr) != &phdr)
+ continue;
+ if (phdr.p_type != PT_LOAD || (phdr.p_flags & PF_X) == 0
+ || phdr.p_filesz == 0 || phdr.p_offset > raw_size
+ || phdr.p_filesz > raw_size - phdr.p_offset)
+ continue;
+ const uint8_t *const start
+ = reinterpret_cast<const uint8_t *> (raw) + phdr.p_offset;
+ segments_.push_back (
+ {phdr.p_vaddr, std::vector<uint8_t> (start, start + phdr.p_filesz)});
+ }
+
+ elf_end (elf);
+ close (fd);
+ return !segments_.empty ();
+}
+
+bool
+arm_text_image::can_fall_through (uint64_t addr) const
+{
+ for (const segment &seg : segments_)
+ {
+ if (addr < seg.vaddr)
+ continue;
+ const uint64_t off = addr - seg.vaddr;
+ if (off > seg.bytes.size ()
+ || AARCH64_INSN_SIZE > seg.bytes.size () - off)
+ continue;
+ const uint8_t *const p = seg.bytes.data () + off;
+ const uint32_t insn = static_cast<uint32_t> (p[0])
+ | (static_cast<uint32_t> (p[1]) << 8)
+ | (static_cast<uint32_t> (p[2]) << 16)
+ | (static_cast<uint32_t> (p[3]) << 24);
+ return aarch64_insn_falls_through (insn);
+ }
+ return true;
+}
+
+void
+arm_spe_synthesize_ranges (
+ const branch_stack_edge_count_map &branch_counts,
+ const std::unordered_map<uint64_t, uint64_t> ¬_taken_counts,
+ const arm_text_image &text, branch_stack_edge_count_map *range_counts)
+{
+ if (range_counts == nullptr || branch_counts.empty ())
+ return;
+
+ /* Synthesize execution ranges from branch landings. */
+ std::unordered_map<uint64_t, uint64_t> incoming_weights;
+ incoming_weights.reserve (branch_counts.size ());
+ for (const auto &entry : branch_counts)
+ incoming_weights[entry.first.second] += entry.second;
+
+ std::vector<uint64_t> landings;
+ landings.reserve (incoming_weights.size ());
+ for (const auto &entry : incoming_weights)
+ landings.push_back (entry.first);
+ std::sort (landings.begin (), landings.end ());
+
+ std::unordered_set<uint64_t> branch_pc_set;
+ branch_pc_set.reserve (branch_counts.size () + not_taken_counts.size ());
+ for (const auto &entry : branch_counts)
+ branch_pc_set.insert (entry.first.first);
+ for (const auto &entry : not_taken_counts)
+ branch_pc_set.insert (entry.first);
+
+ std::vector<uint64_t> branch_addrs (
+ branch_pc_set.begin (), branch_pc_set.end ());
+ std::sort (branch_addrs.begin (), branch_addrs.end ());
+
+ for (size_t i = 0; i < landings.size (); ++i)
+ {
+ const uint64_t landing = landings[i];
+ const uint64_t window = landing > UINT64_MAX - MAX_SPE_RANGE_BYTES
+ ? UINT64_MAX
+ : landing + MAX_SPE_RANGE_BYTES;
+ const uint64_t scan_end = (i + 1 < landings.size ())
+ ? landings[i + 1]
+ : window;
+ uint64_t range_start = landing;
+ uint64_t range_weight = incoming_weights[landing];
+
+ while (range_weight > 0 && range_start < scan_end)
+ {
+ const auto next_branch = std::lower_bound (
+ branch_addrs.begin (), branch_addrs.end (), range_start);
+ const bool hit_branch
+ = next_branch != branch_addrs.end () && *next_branch < scan_end;
+ if (!hit_branch && scan_end - range_start < AARCH64_INSN_SIZE)
+ break;
+ const uint64_t range_end
+ = hit_branch ? *next_branch : scan_end - AARCH64_INSN_SIZE;
+
+ if (range_end >= range_start)
+ (*range_counts)[{range_start, range_end}] += range_weight;
+
+ if (!hit_branch)
+ break;
+
+ /* Continue past a branch only with its sampled not-taken weight, and
+ never past an unconditional transfer. */
+ const auto nit = not_taken_counts.find (*next_branch);
+ range_weight = (nit != not_taken_counts.end ()) ? nit->second : 0;
+ if (!text.can_fall_through (*next_branch))
+ break;
+ if (*next_branch > UINT64_MAX - AARCH64_INSN_SIZE)
+ break;
+ range_start = *next_branch + AARCH64_INSN_SIZE;
+ }
+ }
+}
+
+} /* namespace perf_data. */
diff --git a/afdo_tools/src/arm_spe_aux_decode.h
b/afdo_tools/src/arm_spe_aux_decode.h
new file mode 100644
index 00000000000..58e78e0dded
--- /dev/null
+++ b/afdo_tools/src/arm_spe_aux_decode.h
@@ -0,0 +1,100 @@
+/* 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_ARM_SPE_AUX_DECODE_H
+#define GCC_ARM_SPE_AUX_DECODE_H
+
+#include "perf.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace perf_data {
+
+/* AArch64 instructions have a fixed four-byte width. */
+constexpr uint64_t AARCH64_INSN_SIZE = 4;
+
+/* Filled from SPE AUXTRACE. Callbacks borrow CTX for address filtering and
+ timestamp recording; taken edges go in branch_counts and conditional
+ not-taken branch PCs in not_taken_counts. */
+struct arm_spe_profile_accumulator
+{
+ bool (*accept_addr) (uint64_t runtime_addr, void *ctx) = nullptr;
+ void (*note_timestamp) (uint64_t runtime_addr, uint64_t timestamp, void *ctx)
+ = nullptr;
+ void *ctx = nullptr;
+ branch_stack_edge_count_map *branch_counts = nullptr;
+ std::unordered_map<uint64_t, uint64_t> *not_taken_counts = nullptr;
+};
+
+/* Streaming SPE decoder. Packet fragments and the record under construction
+ survive AUXTRACE chunk boundaries. */
+class arm_spe_stream_decoder
+{
+ std::vector<uint8_t> pending_;
+ uint64_t stream_offset_ = 0;
+ uint64_t from_ip_ = 0;
+ uint64_t to_ip_ = 0;
+ uint64_t events_ = 0;
+ uint64_t op_payload_ = 0;
+ uint64_t timestamp_ = 0;
+ unsigned char op_index_ = 0;
+ bool have_op_ = false;
+
+ void clear_record ();
+ void commit_record (arm_spe_profile_accumulator *acc);
+
+public:
+ /* Consume the next contiguous bytes in the SPE stream. */
+ bool consume (const uint8_t *spe_buf, size_t spe_len,
+ arm_spe_profile_accumulator *acc);
+
+ /* False when the stream ends in the middle of a packet. */
+ bool finish () const { return pending_.empty (); }
+};
+
+/* Profiled binary executable bytes, keyed by static vaddr. */
+class arm_text_image
+{
+ struct segment
+ {
+ uint64_t vaddr;
+ std::vector<uint8_t> bytes;
+ };
+ std::vector<segment> segments_;
+
+public:
+ bool load (const std::string &binary_path);
+
+ /* False only for an AArch64 unconditional transfer; unknown addrs stay
+ true. */
+ bool can_fall_through (uint64_t addr) const;
+};
+
+/* LBR-style fall-through ranges; stop where TEXT cannot fall through. */
+void arm_spe_synthesize_ranges (
+ const branch_stack_edge_count_map &branch_counts,
+ const std::unordered_map<uint64_t, uint64_t> ¬_taken_counts,
+ const arm_text_image &text, branch_stack_edge_count_map *range_counts);
+
+} /* namespace perf_data. */
+
+#endif /* GCC_ARM_SPE_AUX_DECODE_H. */
--
2.34.1