https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128210

            Bug ID: 128210
           Summary: [debug] AArch64: stale local variable and parameter
                    values with -O3
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: debug
          Assignee: unassigned at gcc dot gnu.org
          Reporter: MrEven132 at outlook dot com
  Target Milestone: ---

Debugger: GNU GDB 17.2

With -O3, GDB displays stale values at two source positions in the same
program: value and copy are displayed as -1 on line 9 in update_result, and
index is displayed as 0 on line 20 in run_loop. With -O2, the values are 0, 0
and 1, respectively. The actual frame file, function and line are checked at
both stops; they are not inferred from the requested breakpoints.

Both symptoms disappear with -O3 -fno-schedule-insns and appear with -O2
-fschedule-insns. They exhibit the same scheduling/debug-location pattern,
although the precise GCC implementation defect has not been established.

Testcase
========

Save the following as case.c:

static int ready;
static int result;
static int update_result(void)
{
    int value = -1;
    int copy = -1;
    ready = 1;
    value = 0;
    result = (copy = value) | 2;
    return value;
}

static int marker;
static int run_loop(int index)
{
    for (index = 0; index < 1; ++index) {
        marker = 7;
    }
    return 0;
}

int main(void)
{
    update_result();
    run_loop(0);
    return ready != 1 || result != 2 || marker != 7;
}

Compilation
===========

For an AArch64 target:

gcc -g -gdwarf-5 -gno-statement-frontiers -fno-inline -fno-ipa-sra -O2 case.c
-o O2.elf
gcc -g -gdwarf-5 -gno-statement-frontiers -fno-inline -fno-ipa-sra -O3 case.c
-o O3.elf

The same source and flags are used for both functions. -fno-inline retains the
separate functions, -fno-ipa-sra retains the parameter for inspection, and
-gno-statement-frontiers allows GDB source-line breakpoints to select the
relevant line-table entries. Both executables finish normally with exit status
zero.

Debugger reproduction
=====================

Open each executable in GDB and install both source-line breakpoints:

gdb -q O3.elf
(gdb) break case.c:9
(gdb) break case.c:20
(gdb) run
(gdb) frame
(gdb) info line *$pc
(gdb) print value
(gdb) print copy
(gdb) continue
(gdb) frame
(gdb) info line *$pc
(gdb) print index
(gdb) continue

Repeat the same commands with O2.elf. Neither breakpoint specifies a column or
instruction address. Each function provides a separate reproduction point in
the same executable.

Reproduction point 1: update_result, line 9
==========================================

-O2 captured output:

(gdb) frame
#0  update_result () at case.c:9
9           result = (copy = value) | 2;
(gdb) info line *$pc
Line 9 of "case.c" starts at address 0x4006b0 <update_result+16> and ends at
0x4006b8 <update_result+24>.
(gdb) x/i $pc
=> 0x4006b0 <update_result+16>: mov     w0, #0x2                        // #2
(gdb) print value
$3 = 0
(gdb) print copy
$4 = 0

-O3 captured output:

(gdb) frame
#0  update_result () at case.c:9
9           result = (copy = value) | 2;
(gdb) info line *$pc
Line 9 of "case.c" starts at address 0x4006ac <update_result+12> and ends at
0x4006b0 <update_result+16>.
(gdb) x/i $pc
=> 0x4006ac <update_result+12>: mov     w2, #0x2                        // #2
(gdb) print value
$3 = -1
(gdb) print copy
$4 = -1

Line 8 assigns zero to value before the statement on line 9. Thus value should
be displayed as 0 at line 9, rather than its initial value -1. The statement on
line 9 also assigns value to copy; copy is shown here as additional evidence,
while the already completed assignment to value is sufficient to demonstrate
the stale-value symptom.

Reproduction point 2: run_loop, line 20
======================================

-O2 captured output:

(gdb) frame
#0  run_loop (index=1) at case.c:20
20      }
(gdb) info line *$pc
Line 20 of "case.c" starts at address 0x4006cc <run_loop+12> and ends at
0x4006d4 <_fini>.
(gdb) x/i $pc
=> 0x4006cc <run_loop+12>:      mov     w0, #0x0                        // #0
(gdb) print index
$6 = 1

-O3 captured output:

(gdb) frame
#0  run_loop (index=0) at case.c:20
20      }
(gdb) info line *$pc
Line 20 of "case.c" starts at address 0x4006c8 <run_loop+8> and ends at
0x4006cc <run_loop+12>.
(gdb) x/i $pc
=> 0x4006c8 <run_loop+8>:       mov     w0, #0x0                        // #0
(gdb) print index
$6 = 0

At the closing brace on line 20, the loop has completed in source order and
index should be 1. GDB instead displays 0 with -O3. The closing brace is
associated with function-epilogue instructions; the function has not yet
returned to its caller at the breakpoint.

Absolute source paths have been shortened to case.c in these excerpts. The
capture script reads the variables before printing the full diagnostic
transcript, so its GDB value-history numbers differ from a manual session. The
actual frame source lines match the requests on both sides. Both reproduction
points were observed in each of three complete runs for each optimization
level, with normal exit status zero.

Instruction ordering and DWARF
=============================

In update_result, the relevant -O2 sequence is:

0x4006ac: str w2, [x0, #0x2c]     // line 7: ready = 1
0x4006b0: mov w0, #2              // line 9; breakpoint
0x4006b4: str w0, [x1, #4]        // line 9: store to result

With -O3, the instruction preparing the line-9 value is moved ahead of the
line-7 store:

0x4006ac: mov w2, #2              // line 9; breakpoint
0x4006b0: str w3, [x0, #0x2c]     // line 7: ready = 1
0x4006b4: mov w0, #0              // line 11: return-value preparation
0x4006b8: str w2, [x1, #4]        // line 9: store to result

The value and copy location lists are identical within each optimization level:

-O2, value and copy:
  DW_AT_location:
    [0x4006a0, 0x4006b0): DW_OP_const1s -1, DW_OP_stack_value
    [0x4006b0, 0x4006c0): DW_OP_lit0, DW_OP_stack_value

-O3, value and copy:
  DW_AT_location:
    [0x4006a0, 0x4006b4): DW_OP_const1s -1, DW_OP_stack_value
    [0x4006b4, 0x4006c0): DW_OP_lit0, DW_OP_stack_value

Thus the O3 breakpoint is attributed to line 9 before the location lists switch
to the updated value.

In run_loop, emitted as run_loop.constprop.0, the relevant -O2 sequence is:

0x4006c8: str w1, [x0, #0x34]     // line 17: marker = 7
0x4006cc: mov w0, #0              // line 20; breakpoint
0x4006d0: ret                    // line 20

With -O3, the return-value preparation is moved ahead of the marker store:

0x4006c8: mov w0, #0              // line 20; breakpoint
0x4006cc: str w2, [x1, #0x34]     // line 17: marker = 7
0x4006d0: ret                    // line 20

The index location lists are:

-O2, index:
  DW_AT_location:
    [0x4006c0, 0x4006cc): DW_OP_lit0, DW_OP_stack_value
    [0x4006cc, 0x4006d4): DW_OP_lit1, DW_OP_stack_value

-O3, index:
  DW_AT_location:
    [0x4006c0, 0x4006d0): DW_OP_lit0, DW_OP_stack_value
    [0x4006d0, 0x4006d4): DW_OP_lit1, DW_OP_stack_value

In both functions, an instruction attributed to a later source position is
scheduled before an earlier source-level store, while the variable-update
location ranges remain after that store. GDB follows the constant expressions
in the generated DWARF and displays the earlier values at the later source
position. This supports an interaction between instruction scheduling,
instruction source locations and variable-update debug records; it does not
establish the exact shared GCC root cause.

Optimization controls
=====================

All rows use -g -gdwarf-5 -gno-statement-frontiers -fno-inline -fno-ipa-sra.
Both breakpoints and actual frame lines remain 9 and 20:

Options                         value    copy     index
-O2                             0        0        1
-O3                             -1       -1       0
-O3 -fno-schedule-insns          0        0        1
-O2 -fschedule-insns             -1       -1       0

With x86-64 GCC 17.0.0 20261003 and GDB 17.2, the same source and baseline
options displayed value=0, copy=0 and index=1 at both optimization levels in
three runs; these AArch64 symptoms were not reproduced in that comparison.

O3 line table
=============

The following is the complete decoded line-table row output from the O3
executable used above, compiled with -g -gdwarf-5 -gno-statement-frontiers
-fno-inline -fno-ipa-sra -O3.

Command:

llvm-dwarfdump --debug-line O3.elf

The table uses DWARF version 5. File indices 0 and 1 both refer to case.c.
Original row order, duplicate addresses, flags and both address sequences are
preserved:

Address            Line   Column File   ISA Discriminator OpIndex Flags
------------------ ------ ------ ------ --- ------------- ------- -------------
0x00000000004006a0      4      1      1   0             0       0  is_stmt
0x00000000004006a0      7     11      1   0             0       0  is_stmt
0x00000000004006ac      9     12      1   0             0       0  is_stmt
0x00000000004006b0      7     11      1   0             0       0  is_stmt
0x00000000004006b4     11      1      1   0             0       0  is_stmt
0x00000000004006b8      9     12      1   0             0       0  is_stmt
0x00000000004006bc     11      1      1   0             0       0  is_stmt
0x00000000004006c0     14     12      1   0             0       0  is_stmt
0x00000000004006c0     17     16      1   0         32768       0  is_stmt
0x00000000004006c8     20      1      1   0             0       0  is_stmt
0x00000000004006cc     17     16      1   0         32768       0  is_stmt
0x00000000004006d0     20      1      1   0             0       0  is_stmt
0x00000000004006d4     20      1      1   0             0       0  is_stmt
end_sequence
0x0000000000400500     23      1      1   0             0       0  is_stmt
0x0000000000400508     24      5      1   0             0       0  is_stmt
0x000000000040050c     25      5      1   0             0       0  is_stmt
0x0000000000400510     26     18      1   0             0       0  is_stmt
0x000000000040051c     26     38      1   0             0       0  is_stmt
0x0000000000400524     26     38      1   0             3       0 
0x0000000000400528     27      1      1   0             0       0  is_stmt
0x0000000000400530     26     23      1   0             1       0  is_stmt
0x000000000040053c     26     38      1   0             2       0  is_stmt
0x000000000040054c     26     38      1   0             0       0  is_stmt
end_sequence

The first stop is 0x4006ac, line 9, followed by line 7 at 0x4006b0. The second
stop is 0x4006c8, line 20, followed by line 17 at 0x4006cc. Both breakpoint
rows have is_stmt set. end_sequence rows mark address-sequence boundaries
rather than additional instructions.

Reply via email to