]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
perf annotate-data: Use DWARF location ranges to preserve reg state
authorZecheng Li <zli94@ncsu.edu>
Mon, 9 Mar 2026 17:55:23 +0000 (13:55 -0400)
committerNamhyung Kim <namhyung@kernel.org>
Thu, 19 Mar 2026 21:42:29 +0000 (14:42 -0700)
When a function call occurs, caller-saved registers are typically
invalidated since the callee may clobber them. However, DWARF debug info
provides location ranges that indicate exactly where a variable is valid
in a register.

Track the DWARF location range end address in type_state_reg and use it
to determine if a caller-saved register should be preserved across a
call. If the current call address is within the DWARF-specified lifetime
of the variable, keep the register state valid instead of invalidating
it.

This improves type annotation for code where the compiler knows a
register value survives across calls (e.g., when the callee is known not
to clobber certain registers or when the value is reloaded after the
call at the same logical location).

Changes:
- Add `end` and `has_range` fields to die_var_type to capture DWARF
  location range information
- Add `lifetime_active` and `lifetime_end` fields to type_state_reg
- Check location lifetime before invalidating caller-saved registers

Signed-off-by: Zecheng Li <zli94@ncsu.edu>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
tools/perf/util/annotate-arch/annotate-x86.c
tools/perf/util/annotate-data.c
tools/perf/util/annotate-data.h
tools/perf/util/dwarf-aux.c
tools/perf/util/dwarf-aux.h

index df9fc0a51b39c24e4dd8061acec1f39afc2dd404..c77aabd48ebab69364e19ac52b81b203b663e4f5 100644 (file)
@@ -208,6 +208,8 @@ static void invalidate_reg_state(struct type_state_reg *reg)
 {
        reg->kind = TSR_KIND_INVALID;
        reg->ok = false;
+       reg->lifetime_active = false;
+       reg->lifetime_end = 0;
        reg->copied_from = -1;
 }
 
@@ -230,6 +232,7 @@ static void update_insn_state_x86(struct type_state *state,
        if (ins__is_call(&dl->ins)) {
                struct symbol *func = dl->ops.target.sym;
                const char *call_name;
+               u64 call_addr;
 
                /* Try to resolve the call target name */
                if (func)
@@ -246,10 +249,18 @@ static void update_insn_state_x86(struct type_state *state,
                else
                        pr_debug_dtp("call [%x] <unknown>\n", insn_offset);
 
-               /* Invalidate caller-saved registers after call (ABI requirement) */
+               /* Invalidate caller-saved registers after call */
+               call_addr = map__rip_2objdump(dloc->ms->map,
+                                             dloc->ms->sym->start + dl->al.offset);
                for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) {
-                       if (state->regs[i].caller_saved)
-                               invalidate_reg_state(&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);
                }
 
                /* Update register with the return type (if any) */
@@ -279,6 +290,8 @@ static void update_insn_state_x86(struct type_state *state,
 
                tsr = &state->regs[dst->reg1];
                tsr->copied_from = -1;
+               tsr->lifetime_active = false;
+               tsr->lifetime_end = 0;
 
                if (src->imm)
                        imm_value = src->offset;
@@ -344,6 +357,8 @@ static void update_insn_state_x86(struct type_state *state,
 
                tsr = &state->regs[dst->reg1];
                tsr->copied_from = -1;
+               tsr->lifetime_active = false;
+               tsr->lifetime_end = 0;
 
                if (src->imm)
                        imm_value = src->offset;
@@ -458,6 +473,8 @@ static void update_insn_state_x86(struct type_state *state,
                        state->regs[dst->reg1].kind = TSR_KIND_CONST;
                        state->regs[dst->reg1].imm_value = 0;
                        state->regs[dst->reg1].ok = true;
+                       state->regs[dst->reg1].lifetime_active = false;
+                       state->regs[dst->reg1].lifetime_end = 0;
                        state->regs[dst->reg1].copied_from = -1;
                        return;
                }
@@ -544,6 +561,8 @@ static void update_insn_state_x86(struct type_state *state,
                tsr->kind = state->regs[src->reg1].kind;
                tsr->imm_value = state->regs[src->reg1].imm_value;
                tsr->offset = state->regs[src->reg1].offset;
+               tsr->lifetime_active = state->regs[src->reg1].lifetime_active;
+               tsr->lifetime_end = state->regs[src->reg1].lifetime_end;
                tsr->ok = true;
 
                /* To copy back the variable type later (hopefully) */
index 50c82c91f82806fe1697c4d6c3f39c0f7aac0e34..1eff0a27237d940925dc6c48b61e294c52d16520 100644 (file)
@@ -840,6 +840,18 @@ static bool die_is_same(Dwarf_Die *die_a, Dwarf_Die *die_b)
        return (die_a->cu == die_b->cu) && (die_a->addr == die_b->addr);
 }
 
+static void tsr_set_lifetime(struct type_state_reg *tsr,
+                            const struct die_var_type *var)
+{
+       if (var && var->has_range && var->end > var->addr) {
+               tsr->lifetime_active = true;
+               tsr->lifetime_end = var->end;
+       } else {
+               tsr->lifetime_active = false;
+               tsr->lifetime_end = 0;
+       }
+}
+
 /**
  * update_var_state - Update type state using given variables
  * @state: type state table
@@ -865,8 +877,14 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
        }
 
        for (var = var_types; var != NULL; var = var->next) {
-               if (var->addr != addr)
-                       continue;
+               /* Check if addr falls within the variable's valid range */
+               if (var->has_range) {
+                       if (addr < var->addr || (var->end && addr >= var->end))
+                               continue;
+               } else {
+                       if (addr != var->addr)
+                               continue;
+               }
                /* Get the type DIE using the offset */
                if (!dwarf_offdie(dloc->di->dbg, var->die_off, &mem_die))
                        continue;
@@ -923,6 +941,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
                                reg->type = mem_die;
                                reg->kind = TSR_KIND_POINTER;
                                reg->ok = true;
+                               tsr_set_lifetime(reg, var);
 
                                pr_debug_dtp("var [%"PRIx64"] reg%d addr offset %x",
                                             insn_offset, var->reg, var->offset);
@@ -939,6 +958,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo
                        reg->type = mem_die;
                        reg->kind = TSR_KIND_TYPE;
                        reg->ok = true;
+                       tsr_set_lifetime(reg, var);
 
                        pr_debug_dtp("var [%"PRIx64"] reg%d offset %x",
                                     insn_offset, var->reg, var->offset);
index 9b222869e42d3c6e1ae7a65436e3e73d8f0f0f6a..c26130744260955feb56e7c94a15ef1f02e337a8 100644 (file)
@@ -182,6 +182,9 @@ struct type_state_reg {
        s32 offset;
        bool ok;
        bool caller_saved;
+       /* DWARF location range tracking for register lifetime */
+       bool lifetime_active;
+       u64 lifetime_end;
        u8 kind;
        u8 copied_from;
 };
index 1feefc329154fe8a7d89782340d1e905d0760b24..0710c875416fa7d89866d72be992c4a8c2513e05 100644 (file)
@@ -1641,7 +1641,7 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
        Dwarf_Die type_die;
        int tag = dwarf_tag(die_mem);
        Dwarf_Attribute attr;
-       Dwarf_Addr base, start, end;
+       Dwarf_Addr base, start, end = 0;
        Dwarf_Op *ops;
        size_t nops;
        struct die_var_type *vt;
@@ -1681,6 +1681,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg)
 
        vt->die_off = dwarf_dieoffset(&type_die);
        vt->addr = start;
+       vt->end = end;
+       vt->has_range = (end != 0 || start != 0);
        vt->reg = reg_from_dwarf_op(ops);
        vt->offset = offset_from_dwarf_op(ops);
        vt->next = *var_types;
@@ -1743,6 +1745,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
 
        vt->die_off = dwarf_dieoffset(&type_die);
        vt->addr = ops->number;
+       vt->end = 0;
+       vt->has_range = false;
        vt->reg = -1;
        vt->offset = 0;
        vt->next = *var_types;
index 939a59c91796ad135ce6da0b6ca349a7d5f12df3..a79968a2e573b3e15eb64425d00d4c452f39a74a 100644 (file)
@@ -148,10 +148,12 @@ struct die_var_type {
        struct die_var_type *next;
        u64 die_off;
        u64 addr;
+       u64 end;        /* end address of location range */
        int reg;
        int offset;
        /* Whether the register holds a address to the type */
        bool is_reg_var_addr;
+       bool has_range; /* whether end is valid */
 };
 
 /* Return type info of a member at offset */