Issue 230109.1: Values for optimized out arguments

Author: Jakub Jelinek
Champion: Jakub Jelinek
Date submitted: 2023-01-09
Date revised: 2023-10-02
Date closed:
Type: Enhancement
Status: Open
DWARF Version: 6

Section 2.5.1.7, pg 38

If some function has arguments that are never used in the function or which are only used in code which compiler can prove are unreachable and the function isn't accessible from other object files (or compiler creates a local clone of a function), then some compilers can choose not to pass that parameter at all. If that happens, DW_OP_entry_value isn't an option, because when the argument isn't passed at all, there is no register or memory location for it.

Consider:

static __attribute__((noinline)) int foo (int x, int y) { int z = x + y; return x; }

int bar (void) { return foo (3, 17) + foo (4, 18) + foo (5, 19); }

where noinline attribute is just used to keep the example sufficiently small to explain. As y isn't stored anywhere but to a dead variable, there is no need to pass it at all, so compiler can effectively emit

static int foo_alt (int x) { return x; }

and call it 3 times.

Still users might need to ask in a debugger the value of y or z variables and it is possible to provide that value. Unlike DW_OP_entry_value which can be used in debug info consumers in 2 different ways, one is to find the caller (if possible) and if it can be found, look up the register or memory referenced in DW_OP_entry_value in the DW_TAG_call_site_parameter, or (mostly for non-interactive consumers) if we know in advance we'll need DW_OP_entry_value, put a breakpoint at the start of the function and collect there a value, then look up the remembered value when evaluating DW_OP_entry_value, for the case of completely optimized away arguments the latter method is not an option, there is nothing to remember at function entry. Still, the optimized away arguments can be found with the former method.

The following proposal introduces a new DWARF expression Special operation DW_OP_parameter_ref.

In the above testcase, y would have DW_AT_location of

DW_OP_parameter_ref <y die> DW_OP_stack_value

and z would have DW_AT_location of

DW_OP_breg5 <0> DW_OP_parameter_ref <y die> DW_OP_plus DW_OP_stack_value

(where DW_OP_breg5 <0> is just an example from x86_64 how to get value of x). Then, in the call sites of the 3 foo calls, one would have next to

DW_TAG_call_site_parameter
DW_AT_location DW_OP_reg5 // again x86_64 example where x is passed
DW_AT_call_value DW_OP_lit{3,4,5}

for the call site parameter of x also

DW_TAG_call_site_parameter
DW_AT_call_parameter <y die>
DW_AT_call_value DW_OP_lit{17,18,19}

Changes relative to dwarf6-20221116.pdf:

In 2.5.1.7 add:

3. DW_OP_parameter_ref

The DW_OP_parameter_ref operation pushes the value that the described parameter would have if it was actually passed. It has a single operand: In the 32-bit DWARF format, the operand is a 4-byte unsigned value; in the 64-bit DWARF format, it is an 8-byte unsigned value (see Section 7.4 following). The operand is used as the offset of a DW_TAG_formal_parameter debugging information entry in a .debug_info section. When evaluating DW_OP_parameter_ref, the consumer can try to virtually unwind using the Call Frame Information (see Section 6.4 on page 174) and find DW_TAG_call_site_parameter referencing the same DW_TAG_formal_parameter debugging information entry through its DW_AT_call_parameter attribute.

In 7.7.1 add to Table 7.9:

DW_OP_parameter_ref ‡    0xab    1   4- or 8-byte offset of DIE

In D.1.3 add to the end:

DW_OP_parameter_ref y DW_OP_stack_value

The values of parameter y found in DW_TAG_call_site_parameter
of the caller with DW_AT_call_parameter referencing also parameter y.

Add after D.15.2:

D.15.3 Example with optimized out parameter

Consider the C source in figure D.NN:

static int foo (int x, int y) { int z = x + y; return x; }
int bar (void) { return foo (3, 17) + foo (4, 18) + foo (5, 19); }

Figure D.NN: Example with optimized out parameter: Source

where the producer doesn't inline the foo function, but because the y argument is never needed in generated code doesn't pass it at runtime at all. Possible generated code could be:

foo.local.clone:
    return
bar:
    ! Decrease stack pointer to reserve local stack frame
    %reg3 = %reg3 - 32
    [%reg3] = %reg4  ! Save the call preserved register to stack
    %reg0 = 3        ! Load the 1st argument to foo
    call foo.local.clone  ! 17 is never passed
L20:
    %reg4 = %reg0
    %reg0 = 4        ! Load the 1st argument to foo
    call foo.local.clone  ! 18 is never passed
L21:
    %reg4 = %reg4 + %reg0
    %reg0 = 5        ! Load the 1st argument to foo
    call foo.local.clone  ! 19 is never passed
L22:
    %reg0 = %reg4 + %reg0
    %reg4 = [%reg3]  ! Restore original value of call preserved register
    %reg3 = %reg3 + 32 ! Leave stack frame
    return

Figure D.MM: Example with optimized out parameter: Code

Debug information for the parameters of foo function and the local variable z would be:

DW_TAG_formal_parameter
    DW_AT_name "x"
    DW_AT_type(int)
    DW_AT_location(DW_OP_reg0)
DW_TAG_formal_parameter ! y_die
    DW_AT_name "y"
    DW_AT_type(int)
    DW_AT_location(DW_OP_parameter_ref y_die DW_OP_stack_value)
DW_TAG_variable
    DW_AT_name "z"
    DW_AT_type(int)
    DW_AT_location(DW_OP_breg0 0 DW_OP_parameter_ref y_die DW_OP_plus DW_OP_stack_value)

and call site information for the 3 calls:

DW_TAG_call_site
    DW_AT_call_return_pc(L20)
    DW_AT_call_origin(reference to subprogram foo)
    DW_TAG_call_site_parameter
        DW_AT_location(DW_OP_reg0)
        DW_AT_call_value(DW_OP_lit3)
    DW_TAG_call_site_parameter
        DW_AT_call_parameter(y_die)
        DW_AT_call_value(DW_OP_lit17)
DW_TAG_call_site
    DW_AT_call_return_pc(L21)
    DW_AT_call_origin(reference to subprogram foo)
    DW_TAG_call_site_parameter
        DW_AT_location(DW_OP_reg0)
        DW_AT_call_value(DW_OP_lit4)
    DW_TAG_call_site_parameter
        DW_AT_call_parameter(y_die)
        DW_AT_call_value(DW_OP_lit18)
DW_TAG_call_site
    DW_AT_call_return_pc(L22)
    DW_AT_call_origin(reference to subprogram foo)
    DW_TAG_call_site_parameter
        DW_AT_location(DW_OP_reg0)
        DW_AT_call_value(DW_OP_lit5)
    DW_TAG_call_site_parameter
        DW_AT_call_parameter(y_die)
        DW_AT_call_value(DW_OP_lit19)

2023-10-02: Updated examples.