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

Longjun Luo <luolongjuna at gmail dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |luolongjuna at gmail dot com

--- Comment #7 from Longjun Luo <luolongjuna at gmail dot com> ---
Following up on comments 5 and 6, here are two multi-instance testcases for the
reparenting question in comment 6, plus a plain C lexical-block case showing
that the issue is not specific to C++ or constructor clones.

Tested on x86_64 GNU/Linux with GCC 17.0.0 (g:bd921def14a4), GDB 17.2 and
binutils 2.46.1 across unpatched LTO, LTO with attachment 64213, and non-LTO
builds.

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

A. Constructor with separate C1 and C2 instances (-O0)

Because A has a virtual base, A::A emits separate C1 (complete-object, executed
for 'A a') and C2 (base-object, executed for 'B b') bodies sharing static x.
Stopping at repro.C:11, 'print x' gives:

  Instance     Unpatched LTO    With att. 64213  Non-LTO
  -----------  ---------------  ---------------  -------
  C1 (in A a)  <optimized out>  <optimized out>  7
  C2 (in B b)  <optimized out>  7                7

As expected in comment 6, reparenting the single limbo DIE into C2 (emitted
first) fixes C2 while leaving C1 without a location-bearing x DIE.

B. One inline function inlined into two callers (-O2)

f is inlined into both first and second during LGEN. Stopping at repro.C:5 in
each caller:

  Build            Instance   info locals (and MI)       print x
  ---------------  ---------  -------------------------  ------------------
  Unpatched LTO    in first   result=8 (x omitted)       7
                   in second  result=9 (x omitted)       7
  With att. 64213  in first   result=8, f(int)::x=7      No symbol "x" ...
                   in second  result=9 (x omitted)       7
  Non-LTO          in first   result=8, x=7              7
                   in second  result=9, x=7              7

(GDB/MI '-stack-list-locals 1' and '-stack-list-variables 1' likewise omit x
wherever 'info locals' omits it.)

Reparenting moves the single limbo DIE into the DW_TAG_inlined_subroutine in
first, leaving second without one. Placing that DIE directly under
DW_TAG_inlined_subroutine also hits a separate GDB naming quirk: for a variable
inheriting DW_AT_external from its abstract origin, GDB's die_needs_namespace
treats DW_TAG_subprogram and DW_TAG_lexical_block as local scopes, but not
DW_TAG_inlined_subroutine, qualifying the symbol as 'f(int)::x' so unqualified
'print x' fails.

C. Two disjoint lexical blocks in an ordinary C function (-O2, C)

Each lexical block declares its own 'static volatile int x' (initialized to 11
and 22). Stopping at repro.c:7 and repro.c:12:

  Stop     Build            info locals                   print x
  -------  ---------------  ----------------------------  ---------------
  Block 1  Unpatched LTO    x=<optimized out>, result=11  <optimized out>
                            x=11, x=22
           Non-LTO / 64213  x=11, result=11               11
  Block 2  Unpatched LTO    x=<optimized out>, result=33  <optimized out>
                            x=11, x=22
           Non-LTO / 64213  x=22, result=33               22

In unpatched LTO DWARF, both address-bearing x DIEs are attached directly under
the function DIE (via DECL_CONTEXT in flush_limbo_die_list) rather than under
their respective lexical blocks, while the lexical blocks only inherit the
early x DIEs, which have no location. Here each block has a single concrete
instance, so preserving BLOCK_VARS and reparenting in attachment 64213 restores
the expected scoping.

Historical boundary and design notes

For a C++ testcase equivalent to Case C, linked DWARF places each
address-bearing x in its lexical block with GCC 8.1 and 8.5, but at function
scope with GCC 9.1. Non-LTO controls with GCC 8.5 and 9.1 retain lexical
placement. This release-level boundary is consistent with the BLOCK_VARS change
in r9-1322-g694dc72ebe8701 noted in comment 6. The original constructor
testcase already has the scope-placement problem in GCC 8.1's DWARF, so the
8-to-9 boundary applies specifically to the nested-block testcase.

Related cases filed while investigating this issue:
- PR128622: cross-TU case where the prevailing function body (from b.o) and
local static (from a.o) come from different inputs. Reparenting the limbo DIE
into the executing function makes x visible there, but places a variable whose
origin is in a.C under a function whose origin is in b.C, triggering a GDB
"different parents" complaint.
- PR128619: duplicated constructor/destructor parameters and locals under LTO
(including the duplicated 'this' on this PR's original testcase and on both
instances in Case A above).
- PR128621: the same find_decls_types_r loop also strips TYPE_DECLs, allowing
later scope cleanup to prune a typedef-only outer block, which causes duplicate
locals in GDB.

Cases A and B and PR128622 show the limits of reparenting a single
location-bearing DIE. Following the direction suggested at the end of comment
5, I am preparing an RFC exploring per-instance DIEs for local statics: each
refers to the corresponding variable in the instance's early scope, while its
DW_AT_location describes the selected storage definition (for inline instances,
a direct child of DW_TAG_inlined_subroutine also hits the GDB naming quirk in
Case B).

Reproducers

Save each case in a separate directory.

=== Case A ===

--- repro.C ---
volatile int sink;
struct V { int value; };
struct A : virtual V
{
  A ();
};
A::A ()
{
  static volatile int x = 7;
  sink = x;
  asm volatile ("nop" : : : "memory");
}
struct B : A {};
int main ()
{
  A a;
  B b;
  return sink != 7;
}

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

"${CXX:-g++}" -O0 -g -gdwarf-5 "$mode" -flto-partition=one \
    -no-pie repro.C -o repro

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

Run with:
  CXX=g++ GDB=gdb sh run.sh -flto
  readelf --debug-dump=info --wide repro
  CXX=g++ GDB=gdb sh run.sh -fno-lto

=== Case B ===

--- repro.C ---
inline int f (int n)
{
  static volatile int x = 7;
  int result = x + n;
  asm volatile ("nop" : "+r" (result) : : "memory");
  return result;
}
__attribute__((noipa)) int first (int n) { return f (n); }
__attribute__((noipa)) int second (int n) { return f (n); }
int main ()
{
  int a = first (1);
  int b = second (2);
  return a + b != 17;
}

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

"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" -flto-partition=one \
    -no-pie repro.C -o repro

"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
    -ex 'break repro.C:5' -ex run \
    -ex 'info locals' -ex 'print x' \
    -ex 'interpreter-exec mi "-stack-list-locals 1"' \
    -ex 'interpreter-exec mi "-stack-list-variables 1"' \
    -ex continue -ex 'info locals' -ex 'print x' \
    -ex 'interpreter-exec mi "-stack-list-locals 1"' \
    -ex 'interpreter-exec mi "-stack-list-variables 1"' repro

Run with:
  CXX=g++ GDB=gdb sh run.sh -flto
  readelf --debug-dump=info --wide repro
  CXX=g++ GDB=gdb sh run.sh -fno-lto

=== Case C ===

--- repro.c ---
__attribute__((noinline, noclone)) int f (void)
{
  int result;
  {
    static volatile int x = 11;
    result = x;
    asm volatile ("nop" : "+r" (result) : : "memory");
  }
  {
    static volatile int x = 22;
    result += x;
    asm volatile ("nop" : "+r" (result) : : "memory");
  }
  return result;
}
int main (void) { return f () != 33; }

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

"${CC:-gcc}" -O2 -g -gdwarf-5 "$mode" -flto-partition=one \
    -no-pie repro.c -o repro

"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
    -ex 'break repro.c:7' -ex 'break repro.c:12' -ex run \
    -ex 'info locals' -ex 'print x' \
    -ex continue -ex 'info locals' -ex 'print x' repro

Run with:
  CC=gcc GDB=gdb sh run.sh -flto
  readelf --debug-dump=info --wide repro
  CC=gcc GDB=gdb sh run.sh -fno-lto

Reply via email to