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