[PATCH v5 21/26] perf annotate-arm64: Support stack variable tracking

From: Tengda Wu

Date: Tue Sep 08 2026 - 09:22:26 EST


Extend update_insn_state_arm64() to track data types stored on the
stack.

The implementation handles:
1. Stack Loads: Identify when a register is loaded from a stack
slot and update the register's type state based on the tracked
stack content or compound member types.
2. Stack Stores: Update or create new stack state entries when
a tracked register type is stored to the stack.

Similar to loads, stores also support saving one or two registers
(in the case of 'stp'). Therefore, propagate_store_reg_state() is
introduced.

With these changes, the instruction tracker can now follow data types
as they move between registers and memory, specifically for function
local variables and compiler-spilled values on arm64.

Signed-off-by: Tengda Wu <wutengda@xxxxxxxxxxxxxxx>
---
.../perf/util/annotate-arch/annotate-arm64.c | 159 ++++++++++++++++--
1 file changed, 149 insertions(+), 10 deletions(-)

diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c
index 9d43e2c2041c..d520975c5936 100644
--- a/tools/perf/util/annotate-arch/annotate-arm64.c
+++ b/tools/perf/util/annotate-arch/annotate-arm64.c
@@ -535,6 +535,8 @@ static int propagate_load_reg_state(struct type_state *state,
u32 insn_offset = dl->al.offset;
int sreg = src->reg1;
int reg_offset;
+ int fbreg = dloc->fbreg;
+ int fboff = 0;

if (!has_reg_type(state, dreg))
return -1;
@@ -542,12 +544,57 @@ static int propagate_load_reg_state(struct type_state *state,
tsr = &state->regs[dreg];
tsr->copied_from = -1;

+ if (dloc->fb_cfa) {
+ u64 ip = dloc->ms->sym->start + dl->al.offset;
+ u64 pc = map__rip_2objdump(dloc->ms->map, ip);
+
+ if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0)
+ fbreg = -1;
+ }
+
retry:
if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, &reg_offset))
return -1;

reg_offset += mem_offset;

+ /* Check stack variables with offset */
+ if (sreg == fbreg || sreg == state->stack_reg) {
+ struct type_state_stack *stack;
+ int offset = sreg == fbreg ? reg_offset - fboff : reg_offset;
+
+ stack = find_stack_state(state, offset);
+ if (stack == NULL) {
+ return -1;
+ } else if (!stack->compound) {
+ tsr->type = stack->type;
+ tsr->kind = stack->kind;
+ tsr->offset = stack->ptr_offset;
+ tsr->imm_value = stack->imm_value;
+ tsr->ok = true;
+ } else if (die_get_member_type(&stack->type,
+ offset - stack->offset,
+ &type_die)) {
+ tsr->type = type_die;
+ tsr->kind = TSR_KIND_TYPE;
+ tsr->offset = 0;
+ tsr->imm_value = 0;
+ tsr->ok = true;
+ } else {
+ return -1;
+ }
+
+ if (sreg == fbreg) {
+ pr_debug_dtp("%s [%x] -%#x(stack) -> reg%d",
+ dl->ins.name, insn_offset, -offset, dreg);
+ } else {
+ pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d",
+ dl->ins.name, insn_offset, offset, sreg, dreg);
+ }
+ pr_debug_type_name(&tsr->type, tsr->kind);
+ return 0;
+ }
+
if (!src_tsr || !src_tsr->ok)
return -1;

@@ -663,6 +710,106 @@ static bool is_standard_store_insn(const char *name)
!strncmp(name, "stp", 3); /* stp */
}

+/*
+ * For store insns: propagate type from @sreg to the memory location referenced
+ * by @dst, applying @mem_offset to account for multi-regs stores (e.g. stp).
+ */
+static void propagate_store_reg_state(struct type_state *state,
+ struct data_loc_info *dloc,
+ struct disasm_line *dl, int sreg,
+ struct annotated_op_loc *dst,
+ int mem_offset)
+{
+ struct type_state_reg *tsr;
+ u32 insn_offset = dl->al.offset;
+ int dreg = dst->reg1;
+ int reg_offset;
+ int fbreg = dloc->fbreg;
+ int fboff = 0;
+
+ if (arch_get_reg_offset(dloc->arch, dst, dreg, state, false, &reg_offset))
+ return;
+
+ reg_offset += mem_offset;
+
+ if (dloc->fb_cfa) {
+ u64 ip = dloc->ms->sym->start + dl->al.offset;
+ u64 pc = map__rip_2objdump(dloc->ms->map, ip);
+
+ if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0)
+ fbreg = -1;
+ }
+
+ /* Check stack variables with offset */
+ if (dreg == fbreg || dreg == state->stack_reg) {
+ struct type_state_stack *stack;
+ int offset = dreg == fbreg ? reg_offset - fboff : reg_offset;
+
+ if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) {
+ stack = find_stack_state(state, offset);
+ /* Preserve compound states when only a member is overwritten. */
+ if (stack && !stack->compound)
+ delete_stack_state(state, offset);
+
+ return;
+ }
+
+ tsr = &state->regs[sreg];
+
+ stack = find_stack_state(state, offset);
+ if (stack) {
+ if (!stack->compound)
+ set_stack_state(stack, offset, tsr->kind, &tsr->type,
+ tsr->offset, tsr->imm_value);
+ /*
+ * If it's a compound type, it means attempting to
+ * write to a member value of the compound type without
+ * changing the compound type itself, so do nothing.
+ */
+ } else {
+ findnew_stack_state(state, offset, tsr->kind, &tsr->type,
+ tsr->offset, tsr->imm_value);
+ }
+
+ if (dreg == fbreg) {
+ pr_debug_dtp("%s [%x] reg%d -> -%#x(stack)",
+ dl->ins.name, insn_offset, sreg, -offset);
+ } else {
+ pr_debug_dtp("%s [%x] reg%d -> %#x(reg%d)",
+ dl->ins.name, insn_offset, sreg, offset, dreg);
+ }
+ if (tsr->offset != 0)
+ pr_debug_dtp(" reg%d offset %#x ->", sreg, tsr->offset);
+
+ pr_debug_type_name(&tsr->type, tsr->kind);
+ }
+ /*
+ * Ignore other transfers since it'd set a value in a struct
+ * and won't change the type.
+ */
+}
+
+static void update_store_insn_state(struct type_state *state,
+ struct data_loc_info *dloc,
+ struct disasm_line *dl,
+ struct annotated_op_loc *src,
+ struct annotated_op_loc *dst)
+{
+ /* Handle the first source register */
+ propagate_store_reg_state(state, dloc, dl, src->reg1, dst, /*mem_offset=*/0);
+
+ /* Handle the second source register (if any) */
+ if (src->multi_regs) {
+ int mem_offset = arm64__reg_size(dl->ops.source.raw);
+
+ if (mem_offset >= 0)
+ propagate_store_reg_state(state, dloc, dl, src->reg2,
+ dst, mem_offset);
+ }
+
+ adjust_reg_index_state(state, dloc, dl, dst);
+}
+
static void update_insn_state_arm64(struct type_state *state,
struct data_loc_info *dloc, Dwarf_Die *cu_die,
struct disasm_line *dl)
@@ -745,16 +892,8 @@ static void update_insn_state_arm64(struct type_state *state,
if (is_standard_load_insn(dl->ins.name))
update_load_insn_state(state, dloc, dl, src, dst);
/* Register to memory transfers */
- else if (is_standard_store_insn(dl->ins.name)) {
- /*
- * Ignore transfers since it'd set a value in a struct
- * and won't change the type.
- *
- * Needs to update the pre-index and post-index addressing
- * modes for the destination register.
- */
- adjust_reg_index_state(state, dloc, dl, dst);
- }
+ else if (is_standard_store_insn(dl->ins.name))
+ update_store_insn_state(state, dloc, dl, src, dst);
}
#endif

--
2.34.1