On Tue, 18 Aug 2026, Longjun Luo wrote:

> I have now tried leaving DECL_ABSTRACT_ORIGIN out of the prevailing
> fixup, but that is too late: the reference has already resolved to the
> prevailing FUNCTION_DECL as the tree SCCs are read and unified.  Both
> split clones therefore reach lto_fixup_prevailing_decls with the same
> origin, and the test still fails.

Yeah, it occured to me this would happen... (thanks for keeping
to try ;))

> I then tried the other approach you suggested.  During initial LGEN, the
> prototype records an additional early DIE reference, when available, for
> a public or external FUNCTION_DECL used as DECL_ABSTRACT_ORIGIN or
> BLOCK_ABSTRACT_ORIGIN.  On input, it copies the streamed origin into a
> private, abstract FUNCTION_DECL, associates the saved DIE reference with
> that copy, and uses the copy as the debug origin.  The copy has no symtab
> node and is not registered as a symbol.
> 
> This fixes both the original split-clone mismatch and the same problem in
> an inlined block from the second TU.  I added a scan for the latter as
> well.
> 
> With -r -nostdlib -flinker-output=nolto-rel, the unpatched test links
> successfully but still fails the provenance checks, while the prototype
> passes all 7 checks.  The gcc and g++ LTO suites also complete without
> unexpected results:
> 
>   gcc:  1941 passes, 3 unsupported
>   g++:  1758 passes, 1 expected failure
> 
> The targeted test also passes with aggressive GGC settings under the
> none, one, 1to1, and max LTO partitioning modes.  This is still a
> non-bootstrap prototype; full bootstrap and regression testing will come
> with v4.
> 
> Does the private abstract fake-decl approach match what you had in mind,
> or would you prefer a TREE_DIE_REF or streaming DECL_ABSTRACT_ORIGIN
> through the local section?

So this will probably confuse the streaming machinery quite a bit,
OTOH it seems to be happy to keep local refs to the non-merged
DECL_ABSTRACT_ORIGIN in the PARM_DECLs and BLOCK_VARS.  I wonder
why those are not replaced with the prevailing node.  I notice
(lto_input_ts_block_tree_pointers):

  /* We may end up prevailing a decl with DECL_ORIGIN (t) != t here
     which breaks the invariant that BLOCK_ABSTRACT_ORIGIN is the
     ultimate origin.  Fixup here.
     ???  This should get fixed with moving to DIE references.  */
  if (DECL_P (BLOCK_ORIGIN (expr)))
    BLOCK_ABSTRACT_ORIGIN (expr) = DECL_ORIGIN (BLOCK_ABSTRACT_ORIGIN 
(expr));

that would suggest we do replace global tree references (that's what
I had expected), so it's werid that DECL_ABSTRACT_ORIGIN of the
function decl is replaced to a different node than that of a node
in its body.  We do not seem to have the above issue in
lto_input_ts_decl_common_tree_pointers (for DECL_ABSTRACT_ORIGIN).

Given we are only unifying trees and symbols during WPA (not during
LTRANS), I'm back at failing to see how we can arrive at the
inconsistent state ... (but of course I confirmed it) :/

Richard.

> Longjun Luo <[email protected]> 于2026年8月17日周一 22:34写道:
> 
> > 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)
> >>
> >
> 

-- 
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