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