[PATCH v4 12/23] perf annotate-arm64: Enable instruction tracking support

From: Tengda Wu

Date: Sat Aug 08 2026 - 08:25:46 EST


Enable basic instruction tracking for arm64 by implementing three
essential functions in the find_data_type_block() call path:

find_data_type_block
-> arch_supports_insn_tracking (1)
-> find_data_type_insn
-> init_type_state (2)
-> update_var_state
-> update_insn_state (3)

Changes:

* arch_supports_insn_tracking(): add arm64 to the list of supported
architectures, allowing find_data_type_block() to proceed with data
type analysis.

* init_type_state(): correctly identify ret_reg, stack_reg, and
caller-saved registers for arm64 during type state initialization.

* update_insn_state(): add the update_insn_state_arm64() callback with
conservative register invalidation for call instructions and those with
destination register(s), preventing stale type propagation. Full
instruction-level analysis support will be added incrementally in
later patches.

With these changes, arm64 gains support for basic variable type
inference during instruction tracking.

Signed-off-by: Li Huafei <lihuafei1@xxxxxxxxxx>
Signed-off-by: Tengda Wu <wutengda@xxxxxxxxxxxxxxx>
---
.../perf/util/annotate-arch/annotate-arm64.c | 66 +++++++++++++++++++
tools/perf/util/annotate-data.c | 11 +++-
2 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c
index 6b4f1228afa9..44daa0176e51 100644
--- a/tools/perf/util/annotate-arch/annotate-arm64.c
+++ b/tools/perf/util/annotate-arch/annotate-arm64.c
@@ -9,6 +9,10 @@
#include <regex.h>
#include "../annotate.h"
#include "../disasm.h"
+#include "../annotate-data.h"
+#include "../debug.h"
+#include "../map.h"
+#include "../symbol.h"

struct arch_arm64 {
struct arch arch;
@@ -353,6 +357,65 @@ static int extract_op_location_arm64(const struct arch *arch,
return 0;
}

+#ifdef HAVE_LIBDW_SUPPORT
+static void update_insn_state_arm64(struct type_state *state,
+ struct data_loc_info *dloc, Dwarf_Die *cu_die __maybe_unused,
+ struct disasm_line *dl)
+{
+ struct annotated_insn_loc loc;
+ struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET];
+ u32 insn_offset = dl->al.offset;
+
+ if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0)
+ return;
+
+ /*
+ * Invalidate caller-saved registers on function calls per ARM64 AAPCS64
+ * ABI, unless DWARF location info indicates the register remains valid
+ * beyond the call address.
+ */
+ if (ins__is_call(&dl->ins)) {
+ struct symbol *func = dl->ops.target.sym;
+ const char *call_name;
+ u64 call_addr;
+
+ call_name = func ? func->name : dl->ops.target.name;
+ pr_debug_dtp("call [%x] %s\n", insn_offset, call_name ?: "<unknown>");
+
+ /* Invalidate caller-saved registers after call */
+ call_addr = map__rip_2objdump(dloc->ms->map,
+ dloc->ms->sym->start + dl->al.offset);
+ for (unsigned int i = 0; i < ARRAY_SIZE(state->regs); i++) {
+ struct type_state_reg *reg = &state->regs[i];
+
+ if (!reg->caller_saved)
+ continue;
+ /* Keep register valid within DWARF location lifetime */
+ if (reg->lifetime_active && call_addr < reg->lifetime_end)
+ continue;
+ invalidate_reg_state(reg);
+ }
+ return;
+ }
+
+ /*
+ * Invalidate destination register(s) for unsupported instructions to
+ * prevent stale type info from propagating to subsequent instructions.
+ */
+ if (has_reg_type(state, dst->reg1)) {
+ pr_debug_dtp("%s [%x] invalidate reg%d",
+ dl->ins.name, insn_offset, dst->reg1);
+ invalidate_reg_state(&state->regs[dst->reg1]);
+ if (dst->multi_regs) {
+ pr_debug_dtp(" and reg%d", dst->reg2);
+ invalidate_reg_state(&state->regs[dst->reg2]);
+ }
+ pr_debug_dtp("\n");
+ return;
+ }
+}
+#endif
+
const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
@@ -372,6 +435,9 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
arch->objdump.imm_char = '#';
arch->associate_instruction_ops = arm64__associate_instruction_ops;
arch->extract_op_location = extract_op_location_arm64;
+#ifdef HAVE_LIBDW_SUPPORT
+ arch->update_insn_state = update_insn_state_arm64;
+#endif

/* bl, blr */
err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED);
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 104b80d471f1..c51b20a7af9b 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -28,6 +28,7 @@

/* register number of the stack pointer */
#define X86_REG_SP 7
+#define ARM64_REG_SP 31

static void delete_var_types(struct die_var_type *var_types);

@@ -178,6 +179,13 @@ static void init_type_state(struct type_state *state, const struct arch *arch)
state->regs[11].caller_saved = true;
state->ret_reg = 0;
state->stack_reg = X86_REG_SP;
+ } else if (arch__is_arm64(arch)) {
+ int i;
+
+ for (i = 0; i < 18; i++)
+ state->regs[i].caller_saved = true;
+ state->ret_reg = 0;
+ state->stack_reg = ARM64_REG_SP;
}
}

@@ -1437,7 +1445,8 @@ static enum type_match_result find_data_type_insn(struct data_loc_info *dloc,

static int arch_supports_insn_tracking(struct data_loc_info *dloc)
{
- if ((arch__is_x86(dloc->arch)) || (arch__is_powerpc(dloc->arch)))
+ if (arch__is_x86(dloc->arch) || arch__is_powerpc(dloc->arch) ||
+ arch__is_arm64(dloc->arch))
return 1;
return 0;
}
--
2.34.1