perf annotate-data: Use DWARF location ranges to preserve reg state

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>
This commit is contained in:
Zecheng Li
2026-03-09 13:55:23 -04:00
committed by Namhyung Kim
parent d35b0d5877
commit 4fb7eefe6c
5 changed files with 54 additions and 6 deletions

View 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) */

View 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);

View 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;
};

View 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;

View 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 */