Thanks for digging into the DWARF -- this matches what I see.

The v2/v3 fallback fixes the emitted DIE reference, but not the underlying

tree inconsistency: lto_fixup_prevailing_decls replaces the FUNCTION_DECL's

DECL_ABSTRACT_ORIGIN, while the function-local PARM_DECLs and declarations

in BLOCK_VARS retain their physical input-TU origins.


I will first try leaving DECL_ABSTRACT_ORIGIN untouched during prevailing.

That may leave it referring to a non-prevailing FUNCTION_DECL. Does the

ordinary tree reference keep that declaration available for debug purposes,

or must it be preserved explicitly?


If that is insufficient, I will investigate fixing the local origins during

stream-in or streaming DECL_ABSTRACT_ORIGIN through the function-body local

section. I expect the lto_write_tree_1 fallback to become unnecessary and

will verify and remove it.


The v3 posted later only changes the testcase options; the implementation is

unchanged, so this feedback applies to it as well.


For the testcase link failure, I will use

-r -nostdlib -flinker-output=nolto-rel to avoid the unrelated libstdc++

dependency, then verify again that the test fails without the compiler fix

and passes with it.

Richard Biener <[email protected]> 于2026年8月17日周一 22:09写道:

> On Fri, 14 Aug 2026, Longjun Luo wrote:
>
> > IPA split clones are created after early debug generation, so they have
> no
> > early DIE of their own and the exact die_ref_for_decl lookup for such a
> > clone fails during initial LTO streaming.
> >
> > Without a reference of its own, the clone's concrete DIE has to follow
> its
> > origin declaration.  WPA tree merging selects a prevailing origin, and
> the
> > external DIE reference retained for it can name a different input TU,
> while
> > the clone's parameters and local variables keep references to their
> physical
> > input TU.  The concrete subprogram DIE and its children then carry
> abstract
> > origins from different TUs.
>
> I was looking for this in the DWARF for the testcase when not patched.
> I can see
>
>  <1><1c1>: Abbrev Number: 5 (DW_TAG_subprogram)
>     <1c2>   DW_AT_abstract_origin: <0x508>
>     <1c6>   DW_AT_low_pc      : 0x6e
>     <1ce>   DW_AT_high_pc     : 0x5d
>     <1d6>   DW_AT_frame_base  : 1 byte block: 9c
> (DW_OP_call_frame_cfa)
>     <1d8>   DW_AT_call_all_calls: 1
>     <1d8>   DW_AT_sibling     : <0x256>
>  <2><1dc>: Abbrev Number: 2 (DW_TAG_formal_parameter)
>     <1dd>   DW_AT_abstract_origin: <0x6b3>
>     <1e1>   DW_AT_location    : 0xcc (location list)
>     <1e5>   DW_AT_GNU_locviews: 0xc8
>
> where 508 refers to a DIE with specification at 459 (for printable_length)
> where the formal parameter above is part of the 6a6 subprogram DIE
> with specification at 5f7 (also for printable_length).
>
> So the inconsistency arises because the FUNCTION_DECLs
> DECL_ABSTRACT_ORIGIN is subject to LTO symbol merging while the
> function-local streamed PARM_DECLs DECL_ABSTRACT_ORIGIN is not.
>
> As you show DECL_ABSTRACT_ORIGIN, at least in absence of a
> die_ref_for_decl, is a reference to a debug info instance
> (my very original plan was to make a TREE_DIE_REF and put that
> into DECL_ABSTRACT_ORIGIN, keeping only the early DWARF and not
> the trees here).  So my prefered solution would be to not
> replace DECL_ABSTRACT_ORIGIN during unification (short of, again,
> not actually streaming DECL_ABSTRACT_ORIGIN but instead to
> stream a DIE ref only, possibly materializing a fake decl
> for this on read-in).
>
> I think the issue would show in "bogus" tree structures as well,
> meaning the PARM_DECLs refer to different abstract origins
> than the FUNCTION_DECL.  I wonder if we can possibly fixup
> the local PARM_DECLs abstract origin during stream-in.
> BLOCK_VARs should be similarly affected.  Or maybe we should
> stream DECL_ABSTRACT_ORIGIN in the local section in the first
> place?
>
> > Stream the early DIE reference of the clone's DECL_ORIGIN when an exact
> > reference is unavailable.  This has to happen during initial LTO
> streaming,
> > before merging; afterwards the origin's reference no longer identifies
> the
> > clone's physical TU.  Do this at the streaming caller so that
> > die_ref_for_decl keeps its exact-DECL lookup semantics, and restrict the
> > fallback to cgraph_node::split_part clones.
> >
> > Add an LTO regression test that verifies that an address-bearing concrete
> > split-function DIE and its direct children refer to the same physical
> input
> > TU.
> >
> > Bootstrapped and regression-tested on x86_64-pc-linux-gnu (C and C++
> only),
> > with no new failures.  The new test fails without the patch and passes
> with
> > it.
>
> I'll note the testcase fails to link:
>
> FAIL: g++.dg/lto/pr126348 cp_lto_pr126348_0.o-cp_lto_pr126348_2.o link,
> -O2 -g -gdwarf-5 -dA -save-temps -flto -flto-partition=one -fno-ipa-icf
>
> with
>
> ./libstdc++-v3/src/.libs/libstdc++.so: undefined reference to
> `std::__cxx11::basic_string<char, std::char_traits<char>,
> std::allocator<char> >::_M_create_plus(unsigned long, unsigned long)'
>
> maybe you want to add -r -flinker-output=nolto-rel to the set of linker
> options?
>
> >       PR debug/126348
> >
> > gcc/ChangeLog:
> >
> >       * lto-streamer-out.cc (lto_write_tree_1): Use the abstract origin's
> >       DIE reference as a fallback when initially streaming an IPA split
> >       clone.
> >
> > gcc/testsuite/ChangeLog:
> >
> >       * g++.dg/lto/pr126348.h: New test.
> >       * g++.dg/lto/pr126348_0.C: New test.
> >       * g++.dg/lto/pr126348_1.C: New test.
> >       * g++.dg/lto/pr126348_2.C: New test.
> >
> > Signed-off-by: Longjun Luo <[email protected]>
> > ---
> > Apologies for the delayed follow-up; I only saw your reply in the archive
> > today.
> >
> > Changes in v2, addressing your review of v1:
> >
> > - Moved the fallback out of dwarf2out_die_ref_for_decl into the initial
> LTO
> >   streaming caller, so that primitive keeps its exact-DECL lookup
> semantics,
> >   and restricted it to cgraph_node::split_part clones.  This is the more
> >   specific place you asked for.
> >
> > - On your first point: the concrete DIE does refer to an early DIE via
> >   DW_AT_abstract_origin, but after WPA merging that early DIE can live
> in a
> >   different input TU than the one the clone's own parameters and local
> >   variables refer to, and that is the inconsistency being fixed.  The
> >   address-bearing late DIE is preserved: the test now also requires
> >   DW_AT_low_pc or DW_AT_ranges on the concrete split-function DIE, so the
> >   fix cannot be satisfied by an abstract-only DIE.
> >
> > - The test uses three input TUs and checks the concrete subprogram DIE
> and
> >   its direct children against the same TU.
> >
> > The fallback is deliberately limited to split parts, the case covered by
> > the reproducer and regression test.  Other compiler-generated clones
> > without an early DIE have not been audited.
> >
> >  gcc/lto-streamer-out.cc               | 31 +++++++++++++++++++++++++--
> >  gcc/testsuite/g++.dg/lto/pr126348.h   | 28 ++++++++++++++++++++++++
> >  gcc/testsuite/g++.dg/lto/pr126348_0.C | 24 +++++++++++++++++++++
> >  gcc/testsuite/g++.dg/lto/pr126348_1.C | 28 ++++++++++++++++++++++++
> >  gcc/testsuite/g++.dg/lto/pr126348_2.C | 14 ++++++++++++
> >  5 files changed, 123 insertions(+), 2 deletions(-)
> >  create mode 100644 gcc/testsuite/g++.dg/lto/pr126348.h
> >  create mode 100644 gcc/testsuite/g++.dg/lto/pr126348_0.C
> >  create mode 100644 gcc/testsuite/g++.dg/lto/pr126348_1.C
> >  create mode 100644 gcc/testsuite/g++.dg/lto/pr126348_2.C
> >
> > diff --git a/gcc/lto-streamer-out.cc b/gcc/lto-streamer-out.cc
> > index 7afc2673ea2..108945bb93d 100644
> > --- a/gcc/lto-streamer-out.cc
> > +++ b/gcc/lto-streamer-out.cc
> > @@ -726,8 +726,35 @@ lto_write_tree_1 (struct output_block *ob, tree
> expr, bool ref_p)
> >      {
> >        const char *sym;
> >        unsigned HOST_WIDE_INT off;
> > -      if (debug_info_level > DINFO_LEVEL_NONE
> > -       && debug_hooks->die_ref_for_decl (expr, &sym, &off))
> > +      bool have_ref = false;
> > +
> > +      if (debug_info_level > DINFO_LEVEL_NONE)
> > +     {
> > +       have_ref = debug_hooks->die_ref_for_decl (expr, &sym, &off);
> > +
> > +       /* IPA split clones are created after early debug and have no
> > +          early DIE of their own.  During initial LTO streaming,
> preserve
> > +          the physical input TU by using the clone's abstract origin.
> > +          This must happen before WPA tree merging can make the origin
> > +          refer to a prevailing declaration from another input TU.  Keep
> > +          die_ref_for_decl's exact-DECL lookup contract intact and
> > +          restrict the fallback to the split clone that needs it.  */
> > +       if (!have_ref
> > +           && !in_lto_p
> > +           && TREE_CODE (expr) == FUNCTION_DECL)
> > +         {
> > +           cgraph_node *node = cgraph_node::get (expr);
> > +           if (node && node->split_part)
> > +             {
> > +               tree origin = DECL_ORIGIN (expr);
> > +               if (origin != expr)
> > +                 have_ref
> > +                   = debug_hooks->die_ref_for_decl (origin, &sym, &off);
> > +             }
> > +         }
> > +     }
> > +
> > +      if (have_ref)
> >       {
> >         streamer_write_string (ob, ob->main_stream, sym, true);
> >         streamer_write_uhwi (ob, off);
> > diff --git a/gcc/testsuite/g++.dg/lto/pr126348.h
> b/gcc/testsuite/g++.dg/lto/pr126348.h
> > new file mode 100644
> > index 00000000000..eac6698064a
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/lto/pr126348.h
> > @@ -0,0 +1,28 @@
> > +struct Location
> > +{
> > +  const char *file;
> > +  const char *function;
> > +  int line;
> > +};
> > +
> > +extern void fail (int, const char *, const Location &)
> > +  __attribute__ ((noreturn, cold, noipa));
> > +extern void note (const char *, int) __attribute__ ((cold, noipa));
> > +
> > +static const char source_file[] = __BASE_FILE__;
> > +
> > +struct Buffer
> > +{
> > +  int length;
> > +
> > +  int printable_length () const
> > +  {
> > +    if (__builtin_expect (length < 1024, 1))
> > +      return length;
> > +    const Location location = { source_file, __func__, __LINE__ };
> > +    note (location.file, location.line);
> > +    note (location.function, length);
> > +    note (location.file, length + 1);
> > +    fail (3, "length < 1024", location);
> > +  }
> > +};
> > diff --git a/gcc/testsuite/g++.dg/lto/pr126348_0.C
> b/gcc/testsuite/g++.dg/lto/pr126348_0.C
> > new file mode 100644
> > index 00000000000..bca7cada808
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/lto/pr126348_0.C
> > @@ -0,0 +1,24 @@
> > +/* PR debug/126348 */
> > +/* Verify that an out-of-line split function and its direct children
> retain
> > +   the same input TU provenance, and that the function has an
> address-bearing
> > +   late DIE.  */
> > +/* { dg-lto-do link } */
> > +/* { dg-skip-if "No DWARF debug support" { hppa*-*-hpux* } } */
> > +/* { dg-skip-if "AIX DWARF5" { powerpc-ibm-aix* } } */
> > +/* { dg-lto-options { { -O2 -g -gdwarf-5 -dA -save-temps -flto
> -flto-partition=one -fno-ipa-icf } } } */
> > +/* { dg-final { scan-lto-assembler
> "DW_TAG_subprogram\\)(?:\[^\n\]*\n){1,6}\[^\n\]*pr126348_0\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,4}\[^\n\]*DW_AT_(?:low_pc|ranges)(?:\[^\n\]*\n){1,8}\[^\n\]*DW_TAG_formal_parameter\\)(?:\[^\n\]*\n){1,6}\[^\n\]*pr126348_0\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,4}\[^\n\]*DW_TAG_variable\\)(?:\[^\n\]*\n){1,6}\[^\n\]*pr126348_0\[^\n\]*DW_AT_abstract_origin"
> } } */
> > +/* { dg-final { scan-lto-assembler
> "DW_TAG_subprogram\\)(?:\[^\n\]*\n){1,6}\[^\n\]*pr126348_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,4}\[^\n\]*DW_AT_(?:low_pc|ranges)(?:\[^\n\]*\n){1,8}\[^\n\]*DW_TAG_formal_parameter\\)(?:\[^\n\]*\n){1,6}\[^\n\]*pr126348_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,4}\[^\n\]*DW_TAG_variable\\)(?:\[^\n\]*\n){1,6}\[^\n\]*pr126348_1\[^\n\]*DW_AT_abstract_origin"
> } } */
> > +
> > +#include "pr126348.h"
> > +
> > +__attribute__ ((noinline)) int
> > +one (const Buffer &buffer)
> > +{
> > +  return buffer.printable_length ();
> > +}
> > +
> > +__attribute__ ((noinline)) int
> > +one_extra (const Buffer &buffer)
> > +{
> > +  return buffer.printable_length ();
> > +}
> > diff --git a/gcc/testsuite/g++.dg/lto/pr126348_1.C
> b/gcc/testsuite/g++.dg/lto/pr126348_1.C
> > new file mode 100644
> > index 00000000000..bad1a25aa63
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/lto/pr126348_1.C
> > @@ -0,0 +1,28 @@
> > +#include "pr126348.h"
> > +
> > +__attribute__ ((noinline)) int one (const Buffer &);
> > +__attribute__ ((noinline)) int one_extra (const Buffer &);
> > +
> > +void
> > +fail (int, const char *, const Location &)
> > +{
> > +  __builtin_trap ();
> > +}
> > +
> > +void
> > +note (const char *, int)
> > +{
> > +  asm volatile ("" ::: "memory");
> > +}
> > +
> > +__attribute__ ((noinline)) int
> > +two (const Buffer &buffer)
> > +{
> > +  return buffer.printable_length ();
> > +}
> > +
> > +__attribute__ ((noinline)) int
> > +two_extra (const Buffer &buffer)
> > +{
> > +  return buffer.printable_length ();
> > +}
> > diff --git a/gcc/testsuite/g++.dg/lto/pr126348_2.C
> b/gcc/testsuite/g++.dg/lto/pr126348_2.C
> > new file mode 100644
> > index 00000000000..7a4015f3b49
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/lto/pr126348_2.C
> > @@ -0,0 +1,14 @@
> > +#include "pr126348.h"
> > +
> > +__attribute__ ((noinline)) int one (const Buffer &);
> > +__attribute__ ((noinline)) int one_extra (const Buffer &);
> > +__attribute__ ((noinline)) int two (const Buffer &);
> > +__attribute__ ((noinline)) int two_extra (const Buffer &);
> > +
> > +int
> > +main (int argc, char **)
> > +{
> > +  Buffer buffer = { argc };
> > +  return one (buffer) + one_extra (buffer)
> > +      + two (buffer) + two_extra (buffer);
> > +}
> >
>
> --
> Richard Biener <[email protected]>
> SUSE Software Solutions Germany GmbH,
> Frankenstrasse 146, 90461 Nuernberg, Germany;
> GF: Jochen Jaser, Andrew McDonald, Abhinav Puri; (HRB 36809, AG Nuernberg)
>

Reply via email to