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

            Bug ID: 128621
           Summary: [LTO] Pruning a typedef-only lexical block causes
                    duplicate locals in GDB
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: debug
          Assignee: unassigned at gcc dot gnu.org
          Reporter: luolongjuna at gmail dot com
  Target Milestone: ---

In the single-TU testcase below, an outer lexical block contains only a typedef
and wraps a loop block with a local variable 'it'. Compiling with -O2 -g -flto
prunes the outer block from late DWARF while keeping it in early DWARF, causing
GDB to list 'it' twice. Building with -fno-lto lists 'it' once.

Reproduced on x86_64 GNU/Linux with GCC 17.0.0 (g:bd921def14a4), GDB 17.2 and
binutils 2.46.1, and with Ubuntu GCC 13.3.0.

GCC 17 build configuration:
  --enable-languages=c,c++ --disable-bootstrap --disable-multilib
  --disable-nls --disable-werror --enable-checking=yes
  --without-isl --with-system-zlib

Stopping at repro.C:8 on the first iteration, 'info locals' prints:

  With -flto:                   With -fno-lto:
    it = 0x...                    it = 0x...
    it = <optimized out>

('print it' evaluates to the pointer address in both builds.)

In LTO, early DWARF retains the outer block A because its typedef is still in
BLOCK_VARS before IPA, whereas late DWARF omits A and places the concrete loop
block B' directly under F':

  early DWARF:                  late DWARF (DW_AT_abstract_origin):
    F: run                        F': run ----------> F
      A: outer block                B': loop block -> B  (parent is A, not F)
        typedef iterator              it' ----------> it
        B: loop block
          it

Because B' is a child of F' while its abstract origin B is a child of A, GDB's
inherit_abstract_dies cannot match B' against F's children. With 'set
complaints 100', GDB reports:

  Child DIE ... and its abstract origin ... have different parents

and inherits A's abstract subtree alongside B', adding a locationless copy of
'it'.

During LGEN, early DWARF is emitted before free_lang_data runs.
find_decls_types_r (ipa-free-lang-data.cc) then strips TYPE_DECLs from
BLOCK_VARS (the same loop that strips local statics in PR124864), and the
subsequent scope cleanup in remove_unused_scope_block_p (tree-ssa-live.cc)
removes the now-empty block A and reparents the loop block before the function
body is streamed out. In late DWARF, maybe_create_die_with_external_ref uses
the updated BLOCK_SUPERCONTEXT to place B' under F', while its saved
early-DWARF reference still points to B under A.

Without LTO, the TYPE_DECL remains in BLOCK_VARS and keeps A alive. Removing
the typedef and declaring the loop variable as int *it (so early DWARF already
omits A), or keeping a live variable in A (so A is not pruned), also avoids the
mismatch under -flto (with the breakpoint adjusted to the asm line).

Reproducer

--- repro.C ---
volatile int sink;
__attribute__((noipa)) void run (int *data)
{
  {
    typedef int *iterator;
    for (iterator it = data; it != data + 2; ++it) {
      sink = *it;
      asm volatile ("nop" : : "g" (it) : "memory");
    }
  }
}
int main () { int data[2] = {11, 22}; run (data); }

--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C
mode=${1:--flto}

"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" \
    -save-temps -no-pie repro.C -o repro

"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
    -ex 'break repro.C:8' -ex run \
    -ex 'info locals' -ex 'print it' repro

Run with:
  CXX=g++ GDB=gdb sh run.sh -flto
  readelf --debug-dump=info repro
  gdb -q -nx -batch -iex 'set complaints 100' -ex 'break repro.C:8' repro 2>&1
| grep 'different parents'
  CXX=g++ GDB=gdb sh run.sh -fno-lto

Reply via email to