On Wed, 26 Aug 2026, Longjun Luo wrote:

> Early DWARF gives each input TU its own DIE for a mergeable inline
> function.  During WPA, global SCC unification maps those function trees
> to one prevailing declaration.  In this testcase the function-local
> origin trees already have contexts referring to that prevailing function,
> so the semantic tree topology is consistent.  However, the local trees
> retain their TU-specific early-DIE references while the prevailing
> function tree can retain only one.  A concrete function can consequently
> refer to a function DIE from one TU while its children refer to DIEs from
> another.
> 
> Preserve the physical DIE identity on the abstract-origin edge before
> unification loses it.  When a TU-local function declaration or block has
> no early DIE of its own and its origin is a mergeable function, stream the
> origin DIE reference and distinguish it from a self-DIE reference with a
> tag packed into the streamed offset.  Keep these references in a separate
> map keyed by the concrete owner, forward them through WPA, and use them
> during late DWARF output while the semantic origin still matches.
> DECL_ABSTRACT_ORIGIN and BLOCK_ABSTRACT_ORIGIN continue to point to ordinary
> trees.
> 
> LTO transformations can create further abstract-origin edges after input.
> When the inliner remaps a block, creates an inlined-function block, or
> versions a function, carry forward the source edge's DIE reference.  If the
> source is its own origin, carry its self-DIE reference instead.  Extend the
> existing external-DIE hooks with a reference kind rather than adding new
> debug hook targets.
> 
> The tagged offset changes the LTO bytecode, so bump LTO_minor_version.
> 
> Add regression coverage for an out-of-line split function and for both
> single and nested inlined subroutines.
> 
> Bootstrapped and regression-tested on x86_64-pc-linux-gnu with no new
> failures.  The tests report 9 passes and 3 provenance-scan failures without
> the change, and 12 passes with it.
> 
>       PR debug/126348
> 
> gcc/ChangeLog:
> 
>       * debug.cc (debug_false_tree_charstarstar_uhwistar): Add reference kind.
>       (debug_nothing_tree_charstar_uhwi): Likewise.
>       * debug.h (debug_external_ref_kind): New enum.
>       (gcc_debug_hooks): Add a reference kind to die_ref_for_decl and
>       register_external_die.
>       (debug_false_tree_charstarstar_uhwistar): Adjust declaration.
>       (debug_nothing_tree_charstar_uhwi): Likewise.
>       * dwarf2out.cc (external_abstract_origin_ref): New.
>       (external_abstract_origin_map): New.
>       (abstract_origin_for_instance): New.
>       (dwarf2out_die_ref_for_decl): Handle abstract-origin edge references.
>       (dwarf2out_register_external_die): Likewise.
>       (add_abstract_origin_attribute): Accept the concrete instance and use
>       its edge-specific DIE reference when present.
>       (gen_entry_point_die, gen_formal_parameter_die, gen_subprogram_die,
>       gen_variable_die, gen_label_die, gen_lexical_block_die,
>       gen_inlined_subroutine_die): Pass the concrete instance.
>       * lto-streamer-in.cc (lto_read_tree_1): Read abstract-origin edge DIE
>       references.
>       (lto_input_tree): Pass the self-DIE reference kind.
>       * lto-streamer-out.cc (lto_write_tree_1): Capture and write
>       abstract-origin edge DIE references.
>       * lto-streamer.h (LTO_minor_version): Bump.
>       * tree-inline.cc (copy_lto_abstract_origin_ref): New.
>       (remap_block, expand_call_inline, tree_function_versioning): Use it.
> 
> gcc/lto/ChangeLog:
> 
>       * lto-common.cc (process_dref, lto_read_decls): Pass the self-DIE
>       reference kind.
> 
> gcc/testsuite/ChangeLog:
> 
>       * g++.dg/lto/pr126348.h: New test.
>       * g++.dg/lto/pr126348-2_0.C: New test.
>       * g++.dg/lto/pr126348-2_1.C: New test.
>       * g++.dg/lto/pr126348-2_2.C: 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]>
> ---
> I instrumented stream-in to check the tree state you asked about.  For
> the second input TU, the relevant part of the trace is:
> 
>   FN   origin=.../pr126348_0.C.../349
>   PARM origin=.../pr126348_1.C.../362
>        context=.../pr126348_0.C.../349 same-context=1
>   VAR  origin=.../pr126348_1.C.../371
>        context=.../pr126348_0.C.../349 same-context=1
> 
> Here same-context compares the local origin's DECL_CONTEXT with the
> function origin.  The local PARM_DECL and VAR_DECL origins remain
> TU-specific, but their contexts already refer to the same prevailing
> FUNCTION_DECL.
> 
> This is expected from lto_fixup_prevailing_decls: LTO_SET_PREVAIL only
> substitutes public or external VAR_DECLs and FUNCTION_DECLs.  PARM_DECLs,
> local VAR_DECLs, and BLOCKs have no symtab prevailing counterparts.  The
> DECL_CONTEXT references of the local decls, by contrast, resolve to the
> public prevailing function; that is why the trace reports same-context=1.
> 
> The capture condition reflects the same asymmetry: the owner is a block or
> TU-local function, while the origin is a public or external function.  LTO
> may therefore replace the origin with a prevailing tree while leaving the
> owner unmerged.
> 
> This condition on the abstract-origin edge replaces the split-clone-specific
> fallback in v2/v3 and also covers inlined blocks.
> 
> The loss occurs in external_die_map, which holds one sym/off pair per tree.
> Once global SCC unification maps both function trees to the prevailing
> declaration, that tree can retain only the TU0 DIE reference.  The unmerged
> local decls retain their TU1 references, so late DWARF output can combine
> references from two physical input TUs.
> 
> Mapping those local origins to trees under the prevailing function would
> require a separate structural correspondence across TUs.  It would achieve
> consistency only by discarding the TU1 provenance that the local origins
> still carry.  This patch instead preserves the matching TU1 function DIE
> reference on the concrete owner's abstract-origin edge before the global
> function tree is unified, keeping the parent and its children tied to the
> same physical early-DWARF instance.
> 
> The fixup in lto_input_ts_block_tree_pointers restores the ultimate-origin
> tree invariant, but cannot retain that per-TU DIE identity.  The comment for
> that fixup ends by noting that this should be fixed by "moving to DIE
> references".  This patch follows that direction only for abstract-origin
> edges, while leaving DECL_ABSTRACT_ORIGIN and BLOCK_ABSTRACT_ORIGIN as
> ordinary trees.  The fake-decl approach from the earlier prototype has been
> dropped.
> 
> I also checked all five LTO partitioning modes (none, one, 1to1, balanced,
> and max), parallel LTRANS with -flto=32, aggressive GGC settings, and
> two-stage incremental LTO links.
> 
>  gcc/debug.cc                            |  6 +-
>  gcc/debug.h                             | 29 +++++---
>  gcc/dwarf2out.cc                        | 99 +++++++++++++++++++++----
>  gcc/lto-streamer-in.cc                  | 27 +++++--
>  gcc/lto-streamer-out.cc                 | 45 ++++++++++-
>  gcc/lto-streamer.h                      |  2 +-
>  gcc/lto/lto-common.cc                   |  7 +-
>  gcc/testsuite/g++.dg/lto/pr126348-2_0.C | 17 +++++
>  gcc/testsuite/g++.dg/lto/pr126348-2_1.C | 21 ++++++
>  gcc/testsuite/g++.dg/lto/pr126348-2_2.C | 11 +++
>  gcc/testsuite/g++.dg/lto/pr126348.h     | 28 +++++++
>  gcc/testsuite/g++.dg/lto/pr126348_0.C   | 27 +++++++
>  gcc/testsuite/g++.dg/lto/pr126348_1.C   | 28 +++++++
>  gcc/testsuite/g++.dg/lto/pr126348_2.C   | 14 ++++
>  gcc/tree-inline.cc                      | 36 +++++++++
>  15 files changed, 358 insertions(+), 39 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/lto/pr126348-2_0.C
>  create mode 100644 gcc/testsuite/g++.dg/lto/pr126348-2_1.C
>  create mode 100644 gcc/testsuite/g++.dg/lto/pr126348-2_2.C
>  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/debug.cc b/gcc/debug.cc
> index 5e9c7c1a912..ef712a9f1ae 100644
> --- a/gcc/debug.cc
> +++ b/gcc/debug.cc
> @@ -160,14 +160,16 @@ debug_nothing_tree_int (tree decl ATTRIBUTE_UNUSED,
>  }
>  
>  bool
> -debug_false_tree_charstarstar_uhwistar (tree, const char **,
> +debug_false_tree_charstarstar_uhwistar (tree, debug_external_ref_kind,
> +                                     const char **,
>                                       unsigned HOST_WIDE_INT *)
>  {
>    return false;
>  }
>  
>  void
> -debug_nothing_tree_charstar_uhwi (tree, const char *,
> +debug_nothing_tree_charstar_uhwi (tree, debug_external_ref_kind,
> +                               const char *,
>                                 unsigned HOST_WIDE_INT)
>  {
>  }
> diff --git a/gcc/debug.h b/gcc/debug.h
> index e5976f25d5a..893d8e1e57b 100644
> --- a/gcc/debug.h
> +++ b/gcc/debug.h
> @@ -25,6 +25,14 @@
>     way to speak debug to the middle-end once we are able to get rid of
>     the remaining targets.  If you need alternate output formats instead
>     generate them off the DWARF representation.  */
> +
> +/* The kind of early-DWARF reference exchanged through the LTO streamer.  */
> +enum debug_external_ref_kind
> +{
> +  DEBUG_DIE_REF,
> +  DEBUG_ABSTRACT_ORIGIN_REF
> +};
> +
>  struct gcc_debug_hooks
>  {
>    /* Initialize debug output.  MAIN_FILENAME is the name of the main
> @@ -156,13 +164,14 @@ struct gcc_debug_hooks
>                                   tree context, bool child,
>                                   bool implicit);
>  
> -  /* Return true if a DIE for the tree is available and return a symbol
> -     and offset that can be used to refer to it externally.  */
> -  bool (* die_ref_for_decl) (tree, const char **, unsigned HOST_WIDE_INT *);
> +  /* Return true if an external early-DWARF reference of KIND is available
> +     for the tree and return its symbol and offset.  */
> +  bool (* die_ref_for_decl) (tree, debug_external_ref_kind, const char **,
> +                          unsigned HOST_WIDE_INT *);
>  
> -  /* Early debug information for the tree is available at symbol plus
> -     offset externally.  */
> -  void (* register_external_die) (tree, const char *, unsigned 
> HOST_WIDE_INT);
> +  /* Record an external early-DWARF reference of KIND for the tree.  */
> +  void (* register_external_die) (tree, debug_external_ref_kind,
> +                               const char *, unsigned HOST_WIDE_INT);
>  
>    /* DECL is an inline function, whose body is present, but which is
>       not being output at this point.  */
> @@ -232,10 +241,10 @@ extern void debug_nothing_tree_tree_tree_bool_bool 
> (tree, tree, tree,
>  extern bool debug_true_const_tree (const_tree);
>  extern void debug_nothing_rtx_insn (rtx_insn *);
>  extern void debug_nothing_rtx_code_label (rtx_code_label *);
> -extern bool debug_false_tree_charstarstar_uhwistar (tree, const char **,
> -                                                 unsigned HOST_WIDE_INT *);
> -extern void debug_nothing_tree_charstar_uhwi (tree, const char *,
> -                                           unsigned HOST_WIDE_INT);
> +extern bool debug_false_tree_charstarstar_uhwistar
> +  (tree, debug_external_ref_kind, const char **, unsigned HOST_WIDE_INT *);
> +extern void debug_nothing_tree_charstar_uhwi
> +  (tree, debug_external_ref_kind, const char *, unsigned HOST_WIDE_INT);
>  
>  /* Hooks for various debug formats.  */
>  extern const struct gcc_debug_hooks do_nothing_debug_hooks;
> diff --git a/gcc/dwarf2out.cc b/gcc/dwarf2out.cc
> index 0b974d63c80..bac129644cf 100644
> --- a/gcc/dwarf2out.cc
> +++ b/gcc/dwarf2out.cc
> @@ -2883,10 +2883,10 @@ static void dwarf2out_begin_function (tree);
>  static void dwarf2out_end_function (unsigned int);
>  static void dwarf2out_register_main_translation_unit (tree unit);
>  static void dwarf2out_set_name (tree, tree);
> -static void dwarf2out_register_external_die (tree decl, const char *sym,
> -                                          unsigned HOST_WIDE_INT off);
> -static bool dwarf2out_die_ref_for_decl (tree decl, const char **sym,
> -                                     unsigned HOST_WIDE_INT *off);
> +static void dwarf2out_register_external_die
> +  (tree, debug_external_ref_kind, const char *, unsigned HOST_WIDE_INT);
> +static bool dwarf2out_die_ref_for_decl
> +  (tree, debug_external_ref_kind, const char **, unsigned HOST_WIDE_INT *);
>  
>  /* The debug hooks structure.  */
>  
> @@ -3924,7 +3924,7 @@ static void add_alignment_attribute (dw_die_ref, tree);
>  static void add_bit_offset_attribute (dw_die_ref, tree);
>  static void add_bit_size_attribute (dw_die_ref, tree);
>  static void add_prototyped_attribute (dw_die_ref, tree);
> -static void add_abstract_origin_attribute (dw_die_ref, tree);
> +static void add_abstract_origin_attribute (dw_die_ref, tree, tree);
>  static void add_pure_or_virtual_attribute (dw_die_ref, tree);
>  static void add_src_coords_attributes (dw_die_ref, tree);
>  static void add_name_and_src_coords_attributes (dw_die_ref, tree, bool = 
> false);
> @@ -5974,6 +5974,30 @@ struct GTY(()) sym_off_pair
>  };
>  static GTY(()) hash_map<tree, sym_off_pair> *external_die_map;
>  
> +struct GTY(()) external_abstract_origin_ref
> +{
> +  tree origin;
> +  const char *sym;
> +  unsigned HOST_WIDE_INT off;
> +};
> +
> +/* Unlike EXTERNAL_DIE_MAP, this map describes an abstract-origin edge.  Its
> +   key is the concrete decl or block, rather than the referenced origin.  */
> +static GTY(()) hash_map<tree, external_abstract_origin_ref>
> +  *external_abstract_origin_map;
> +
> +/* Return the abstract origin associated with INSTANCE.  */
> +
> +static tree
> +abstract_origin_for_instance (tree instance)
> +{
> +  if (DECL_P (instance))
> +    return DECL_ABSTRACT_ORIGIN (instance);
> +  if (TREE_CODE (instance) == BLOCK)
> +    return BLOCK_ABSTRACT_ORIGIN (instance);
> +  return NULL_TREE;
> +}
> +
>  /* Returns a hash value for X (which really is a die_struct).  */
>  
>  inline hashval_t
> @@ -6038,11 +6062,26 @@ equate_block_to_die (tree block, dw_die_ref die)
>     DECL, otherwise return false.  */
>  
>  static bool
> -dwarf2out_die_ref_for_decl (tree decl, const char **sym,
> +dwarf2out_die_ref_for_decl (tree decl, debug_external_ref_kind kind,
> +                         const char **sym,
>                           unsigned HOST_WIDE_INT *off)
>  {
>    dw_die_ref die;
>  
> +  if (kind == DEBUG_ABSTRACT_ORIGIN_REF)
> +    {
> +      if (!external_abstract_origin_map)
> +     return false;
> +      external_abstract_origin_ref *desc
> +     = external_abstract_origin_map->get (decl);
> +      if (!desc || desc->origin != abstract_origin_for_instance (decl))
> +     return false;
> +      *sym = desc->sym;
> +      *off = desc->off;
> +      return true;
> +    }
> +
> +  gcc_checking_assert (kind == DEBUG_DIE_REF);
>    if (in_lto_p)
>      {
>        /* During WPA stage and incremental linking we use a hash-map
> @@ -6105,12 +6144,28 @@ add_AT_external_die_ref (dw_die_ref die, enum 
> dwarf_attribute attr_kind,
>     at SYMBOL + OFFSET which contains attributes dumped early.  */
>  
>  static void
> -dwarf2out_register_external_die (tree decl, const char *sym,
> +dwarf2out_register_external_die (tree decl, debug_external_ref_kind kind,
> +                              const char *sym,
>                                unsigned HOST_WIDE_INT off)
>  {
>    if (debug_info_level == DINFO_LEVEL_NONE)
>      return;
>  
> +  if (kind == DEBUG_ABSTRACT_ORIGIN_REF)
> +    {
> +      if (!external_abstract_origin_map)
> +     external_abstract_origin_map
> +       = hash_map<tree, external_abstract_origin_ref>::create_ggc (1000);
> +      gcc_checking_assert (!external_abstract_origin_map->get (decl));
> +      external_abstract_origin_ref p
> +     = { abstract_origin_for_instance (decl),
> +         IDENTIFIER_POINTER (get_identifier (sym)), off };
> +      gcc_checking_assert (p.origin);
> +      external_abstract_origin_map->put (decl, p);
> +      return;
> +    }
> +
> +  gcc_checking_assert (kind == DEBUG_DIE_REF);
>    if (!external_die_map)
>      external_die_map = hash_map<tree, sym_off_pair>::create_ggc (1000);
>    gcc_checking_assert (!external_die_map->get (decl));
> @@ -22368,10 +22423,24 @@ add_prototyped_attribute (dw_die_ref die, tree 
> func_type)
>     the block mapping.  */
>  
>  static inline void
> -add_abstract_origin_attribute (dw_die_ref die, tree origin)
> +add_abstract_origin_attribute (dw_die_ref die, tree origin, tree instance)
>  {
>    dw_die_ref origin_die = NULL;
>  
> +  /* A mergeable tree can denote more than one physical early-DWARF
> +     instance.  Prefer the reference recorded for this particular origin
> +     edge when LTO streaming preserved one.  */
> +  const char *sym;
> +  unsigned HOST_WIDE_INT off;
> +  if (in_lto_p
> +      && instance
> +      && dwarf2out_die_ref_for_decl (instance,
> +                                  DEBUG_ABSTRACT_ORIGIN_REF, &sym, &off))
> +    {
> +      add_AT_external_die_ref (die, DW_AT_abstract_origin, sym, off);
> +      return;
> +    }
> +

We still query the regular DIE map below, directly, not via
dwarf2out_die_ref_for_decl.  I'd like to see consistency
here and at least a comment on why that isn't now dead code - should
not all abstract origins be DEBUG_ABSTRACT_ORIGIN_REF now?

>    /* For late LTO debug output we want to refer directly to the abstract
>       DIE in the early debug rather to the possibly existing concrete
>       instance and avoid creating that just for this purpose.  */
> @@ -23198,7 +23267,7 @@ gen_entry_point_die (tree decl, dw_die_ref 
> context_die)
>    dw_die_ref decl_die = new_die (DW_TAG_entry_point, context_die, decl);
>  
>    if (origin != NULL)
> -    add_abstract_origin_attribute (decl_die, origin);
> +    add_abstract_origin_attribute (decl_die, origin, decl);
>    else
>      {
>        add_name_and_src_coords_attributes (decl_die, decl);
> @@ -23473,7 +23542,7 @@ gen_formal_parameter_die (tree node, tree origin, 
> bool emit_name_p,
>       goto add_location;
>  
>        if (origin != NULL)
> -     add_abstract_origin_attribute (parm_die, origin);
> +     add_abstract_origin_attribute (parm_die, origin, node);
>        else if (emit_name_p)
>       add_name_and_src_coords_attributes (parm_die, node);
>        if (origin == NULL
> @@ -24071,7 +24140,7 @@ gen_subprogram_die (tree decl, dw_die_ref context_die)
>        else
>       {
>         subr_die = new_die (DW_TAG_subprogram, context_die, decl);
> -       add_abstract_origin_attribute (subr_die, origin);
> +       add_abstract_origin_attribute (subr_die, origin, decl);
>         /*  This is where the actual code for a cloned function is.
>             Let's emit linkage name attribute for it.  This helps
>             debuggers to e.g, set breakpoints into
> @@ -25099,7 +25168,7 @@ gen_variable_die (tree decl, tree origin, dw_die_ref 
> context_die)
>      var_die = new_die (DW_TAG_variable, context_die, decl);
>  
>    if (origin != NULL)
> -    add_abstract_origin_attribute (var_die, origin);
> +    add_abstract_origin_attribute (var_die, origin, decl);
>  
>    /* Loop unrolling can create multiple blocks that refer to the same
>       static variable, so we must test for the DW_AT_declaration flag.
> @@ -25272,7 +25341,7 @@ gen_label_die (tree decl, dw_die_ref context_die)
>        equate_decl_number_to_die (decl, lbl_die);
>  
>        if (origin != NULL)
> -     add_abstract_origin_attribute (lbl_die, origin);
> +     add_abstract_origin_attribute (lbl_die, origin, decl);
>        else
>       add_name_and_src_coords_attributes (lbl_die, decl);
>      }
> @@ -25496,7 +25565,7 @@ gen_lexical_block_die (tree stmt, dw_die_ref 
> context_die)
>  
>        tree origin = block_ultimate_origin (stmt);
>        if (origin != NULL_TREE && (origin != stmt || old_die))
> -     add_abstract_origin_attribute (stmt_die, origin);
> +     add_abstract_origin_attribute (stmt_die, origin, stmt);
>  
>        old_die = NULL;
>      }
> @@ -25531,7 +25600,7 @@ gen_inlined_subroutine_die (tree stmt, dw_die_ref 
> context_die)
>  
>    if (call_arg_locations || debug_inline_points)
>      equate_block_to_die (stmt, subr_die);
> -  add_abstract_origin_attribute (subr_die, decl);
> +  add_abstract_origin_attribute (subr_die, decl, stmt);
>    if (TREE_ASM_WRITTEN (stmt))
>      add_high_low_attributes (stmt, subr_die);
>    add_call_src_coords_attributes (stmt, subr_die);
> diff --git a/gcc/lto-streamer-in.cc b/gcc/lto-streamer-in.cc
> index 901c3212656..6230a673171 100644
> --- a/gcc/lto-streamer-in.cc
> +++ b/gcc/lto-streamer-in.cc
> @@ -1802,9 +1802,24 @@ lto_read_tree_1 (class lto_input_block *ib, class 
> data_in *data_in, tree expr)
>        const char *str = streamer_read_string (data_in, ib);
>        if (str)
>       {
> -       unsigned HOST_WIDE_INT off = streamer_read_uhwi (ib);
> -       dref_entry e = { expr, str, off };
> -       dref_queue.safe_push (e);
> +       unsigned HOST_WIDE_INT off_and_kind = streamer_read_uhwi (ib);
> +       debug_external_ref_kind ref_kind
> +         = ((off_and_kind & 1)
> +            ? DEBUG_ABSTRACT_ORIGIN_REF : DEBUG_DIE_REF);
> +       unsigned HOST_WIDE_INT off = off_and_kind / 2;
> +       if (ref_kind == DEBUG_ABSTRACT_ORIGIN_REF)
> +         {
> +           /* Origin-edge references are keyed by a concrete TU-local owner.
> +              Register them directly.  DREF_QUEUE may canonicalize its key to
> +              a prevailing tree and is only appropriate for self-DIE
> +              references.  */

But still references from 'expr' are only necessary when 'expr'
prevails?  The point of the dref_queue is to throw them away
when the tree SCC is thrown away.  I don't see where e.decl
is mangled before registering.

I fear doing it the way you do creates a lot of garbage in the
hash-map.

> +           debug_hooks->register_external_die (expr, ref_kind, str, off);
> +         }
> +       else
> +         {
> +           dref_entry e = { expr, str, off };
> +           dref_queue.safe_push (e);
> +         }
>       }
>        /* When there's no early DIE to refer to but dwarf2out set up
>        things in a way to expect that fixup.  This tends to happen
> @@ -2018,7 +2033,8 @@ lto_input_tree (class lto_input_block *ib, class 
> data_in *data_in)
>        while (!dref_queue.is_empty ())
>       {
>         dref_entry e = dref_queue.pop ();
> -       debug_hooks->register_external_die (e.decl, e.sym, e.off);
> +       debug_hooks->register_external_die (e.decl, DEBUG_DIE_REF,
> +                                           e.sym, e.off);
>       }
>      }
>    tree t = lto_input_tree_1 (ib, data_in, tag, 0);
> @@ -2026,7 +2042,8 @@ lto_input_tree (class lto_input_block *ib, class 
> data_in *data_in)
>    if (!dref_queue.is_empty ())
>      {
>        dref_entry e = dref_queue.pop ();
> -      debug_hooks->register_external_die (e.decl, e.sym, e.off);
> +      debug_hooks->register_external_die (e.decl, DEBUG_DIE_REF,
> +                                       e.sym, e.off);
>        gcc_checking_assert (dref_queue.is_empty ());
>      }
>    return t;
> diff --git a/gcc/lto-streamer-out.cc b/gcc/lto-streamer-out.cc
> index 7afc2673ea2..f43acc1fcd2 100644
> --- a/gcc/lto-streamer-out.cc
> +++ b/gcc/lto-streamer-out.cc
> @@ -726,11 +726,50 @@ 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;
> +      debug_external_ref_kind ref_kind = DEBUG_DIE_REF;
> +      if (debug_info_level > DINFO_LEVEL_NONE)
> +     have_ref = debug_hooks->die_ref_for_decl (expr, DEBUG_DIE_REF,
> +                                               &sym, &off);
> +
> +      /* A global function tree can be shared by several input TUs, but an
> +      abstract-origin edge can refer to a particular early-DWARF instance.
> +      Preserve that edge separately for TU-local function clones and blocks
> +      that have no early DIE of their own.  */
> +      tree origin = (DECL_P (expr)
> +                  ? DECL_ABSTRACT_ORIGIN (expr)
> +                  : BLOCK_ABSTRACT_ORIGIN (expr));
> +      bool local_owner
> +     = (TREE_CODE (expr) == BLOCK
> +        || (TREE_CODE (expr) == FUNCTION_DECL
> +            && !TREE_PUBLIC (expr) && !DECL_EXTERNAL (expr)));
> +      bool have_origin_ref = false;
> +      if (!have_ref
> +       && debug_info_level > DINFO_LEVEL_NONE)
> +     {
> +       have_origin_ref = debug_hooks->die_ref_for_decl
> +         (expr, DEBUG_ABSTRACT_ORIGIN_REF, &sym, &off);
> +       if (!have_origin_ref
> +           && !in_lto_p
> +           && local_owner
> +           && origin && origin != expr
> +           && TREE_CODE (origin) == FUNCTION_DECL
> +           && (TREE_PUBLIC (origin) || DECL_EXTERNAL (origin)))

I think amending the comment on why we check for both would be good.
Did you have test coverage for this 2nd level?

> +         have_origin_ref = debug_hooks->die_ref_for_decl
> +           (origin, DEBUG_DIE_REF, &sym, &off);
> +     }
> +      if (have_origin_ref)
> +     {
> +       have_ref = true;
> +       ref_kind = DEBUG_ABSTRACT_ORIGIN_REF;
> +     }
> +
> +      if (have_ref)
>       {
>         streamer_write_string (ob, ob->main_stream, sym, true);
> -       streamer_write_uhwi (ob, off);
> +       streamer_write_uhwi
> +         (ob, off * 2 + (ref_kind == DEBUG_ABSTRACT_ORIGIN_REF));
>       }
>        else
>       streamer_write_string (ob, ob->main_stream, NULL, true);
> diff --git a/gcc/lto-streamer.h b/gcc/lto-streamer.h
> index d1285b87bc5..457c79d1298 100644
> --- a/gcc/lto-streamer.h
> +++ b/gcc/lto-streamer.h
> @@ -122,7 +122,7 @@ along with GCC; see the file COPYING3.  If not see
>       form followed by the data for the string.  */
>  
>  #define LTO_major_version GCC_major_version
> -#define LTO_minor_version 0
> +#define LTO_minor_version 1

No need to bump the minor on trunk.

Otherwise I think the design is sound.

I'll reply to your ping as well soonish.

Thanks,
Richard.

>  
>  typedef unsigned char        lto_decl_flags_t;
>  
> diff --git a/gcc/lto/lto-common.cc b/gcc/lto/lto-common.cc
> index 96bd97f1374..440c8ee3885 100644
> --- a/gcc/lto/lto-common.cc
> +++ b/gcc/lto/lto-common.cc
> @@ -1675,8 +1675,8 @@ process_dref (class data_in *data_in, tree t, unsigned 
> from)
>                 == streamer_tree_cache_get_tree (cache, from));
>        const char *sym;
>        unsigned HOST_WIDE_INT off;
> -      if (!debug_hooks->die_ref_for_decl (t, &sym, &off))
> -     debug_hooks->register_external_die (t, e.sym, e.off);
> +      if (!debug_hooks->die_ref_for_decl (t, DEBUG_DIE_REF, &sym, &off))
> +     debug_hooks->register_external_die (t, DEBUG_DIE_REF, e.sym, e.off);
>      }
>  }
>  
> @@ -1972,7 +1972,8 @@ lto_read_decls (struct lto_file_decl_data *decl_data, 
> const void *data,
>         while (!dref_queue.is_empty ())
>           {
>             dref_entry e = dref_queue.pop ();
> -           debug_hooks->register_external_die (e.decl, e.sym, e.off);
> +           debug_hooks->register_external_die (e.decl, DEBUG_DIE_REF,
> +                                               e.sym, e.off);
>           }
>  
>         if (seen_type)
> diff --git a/gcc/testsuite/g++.dg/lto/pr126348-2_0.C 
> b/gcc/testsuite/g++.dg/lto/pr126348-2_0.C
> new file mode 100644
> index 00000000000..7dd5a3057b4
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/lto/pr126348-2_0.C
> @@ -0,0 +1,17 @@
> +/* PR debug/126348 */
> +/* Verify that nested inlined subroutines and their direct children retain
> +   the same input TU provenance.  */
> +/* { 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 --param=max-inline-insns-auto=100 } } } */
> +/* { dg-extra-ld-options "-r -nostdlib -flinker-output=nolto-rel" } */
> +/* { dg-final { scan-lto-assembler 
> "DW_TAG_inlined_subroutine\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_2_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,10}\[^\n\]*DW_TAG_formal_parameter\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_2_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,8}\[^\n\]*DW_TAG_variable\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_2_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,3}\[^\n\]*DW_TAG_inlined_subroutine\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_2_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,10}\[^\n\]*DW_TAG_formal_parameter\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_2_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,8}\[^\n\]*DW_TAG_variable\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_2_1\[^\n\]*DW_AT_abstract_origin"
>  } } */
> +
> +#include "pr126348.h"
> +
> +__attribute__ ((noinline)) int
> +one (const Buffer &buffer)
> +{
> +  return buffer.printable_length ();
> +}
> diff --git a/gcc/testsuite/g++.dg/lto/pr126348-2_1.C 
> b/gcc/testsuite/g++.dg/lto/pr126348-2_1.C
> new file mode 100644
> index 00000000000..5e871bdc800
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/lto/pr126348-2_1.C
> @@ -0,0 +1,21 @@
> +#include "pr126348.h"
> +
> +__attribute__ ((noinline)) int one (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 ();
> +}
> diff --git a/gcc/testsuite/g++.dg/lto/pr126348-2_2.C 
> b/gcc/testsuite/g++.dg/lto/pr126348-2_2.C
> new file mode 100644
> index 00000000000..f540eef4802
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/lto/pr126348-2_2.C
> @@ -0,0 +1,11 @@
> +#include "pr126348.h"
> +
> +__attribute__ ((noinline)) int one (const Buffer &);
> +__attribute__ ((noinline)) int two (const Buffer &);
> +
> +int
> +main (int argc, char **)
> +{
> +  Buffer buffer = { argc };
> +  return one (buffer) + two (buffer);
> +}
> 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..99387845ae2
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/lto/pr126348_0.C
> @@ -0,0 +1,27 @@
> +/* 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 --param=max-inline-insns-auto=100 } } } */
> +/* { dg-extra-ld-options "-r -nostdlib -flinker-output=nolto-rel" } */
> +/* { 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"
>  } } */
> +
> +/* { dg-final { scan-lto-assembler 
> "DW_TAG_inlined_subroutine\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,10}\[^\n\]*DW_TAG_formal_parameter\\)(?:\[^\n\]*\n){1,3}\[^\n\]*pr126348_1\[^\n\]*DW_AT_abstract_origin(?:\[^\n\]*\n){1,8}\[^\n\]*DW_TAG_variable\\)(?:\[^\n\]*\n){1,3}\[^\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);
> +}
> diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
> index 8162b1f5051..467b69c49be 100644
> --- a/gcc/tree-inline.cc
> +++ b/gcc/tree-inline.cc
> @@ -798,6 +798,39 @@ remap_decls (tree decls, vec<tree, va_gc> 
> **nonlocalized_list,
>    return nreverse (new_decls);
>  }
>  
> +/* Preserve early-DIE provenance when an LTO transformation copies FROM's
> +   abstract origin to TO.  */
> +
> +static void
> +copy_lto_abstract_origin_ref (tree to, tree from)
> +{
> +  if (!in_lto_p)
> +    return;
> +
> +  tree origin;
> +  if (DECL_P (from))
> +    origin = DECL_ABSTRACT_ORIGIN (from);
> +  else
> +    {
> +      gcc_checking_assert (TREE_CODE (from) == BLOCK);
> +      origin = BLOCK_ABSTRACT_ORIGIN (from);
> +    }
> +
> +  const char *sym;
> +  unsigned HOST_WIDE_INT off;
> +  bool have_ref
> +    = debug_hooks->die_ref_for_decl (from, DEBUG_ABSTRACT_ORIGIN_REF,
> +                                  &sym, &off);
> +  /* Without an abstract origin TO points directly to FROM, so FROM's own
> +     early DIE is the origin DIE to preserve.  */
> +  if (!have_ref && (!origin || origin == from))
> +    have_ref = debug_hooks->die_ref_for_decl (from, DEBUG_DIE_REF,
> +                                           &sym, &off);
> +  if (have_ref)
> +    debug_hooks->register_external_die (to, DEBUG_ABSTRACT_ORIGIN_REF,
> +                                     sym, off);
> +}
> +
>  /* Copy the BLOCK to contain remapped versions of the variables
>     therein.  And hook the new block into the block-tree.  */
>  
> @@ -812,6 +845,7 @@ remap_block (tree *block, copy_body_data *id)
>    new_block = make_node (BLOCK);
>    TREE_USED (new_block) = TREE_USED (old_block);
>    BLOCK_ABSTRACT_ORIGIN (new_block) = BLOCK_ORIGIN (old_block);
> +  copy_lto_abstract_origin_ref (new_block, old_block);
>    BLOCK_SOURCE_LOCATION (new_block) = BLOCK_SOURCE_LOCATION (old_block);
>    BLOCK_NONLOCALIZED_VARS (new_block)
>      = vec_safe_copy (BLOCK_NONLOCALIZED_VARS (old_block));
> @@ -5054,6 +5088,7 @@ expand_call_inline (basic_block bb, gimple *stmt, 
> copy_body_data *id,
>               (loc, get_discriminator_from_loc (gimple_location (stmt)));
>        id->block = make_node (BLOCK);
>        BLOCK_ABSTRACT_ORIGIN (id->block) = DECL_ORIGIN (fn);
> +      copy_lto_abstract_origin_ref (id->block, fn);
>        BLOCK_SOURCE_LOCATION (id->block) = loc;
>        prepend_lexical_block (block, id->block);
>      }
> @@ -6381,6 +6416,7 @@ tree_function_versioning (tree old_decl, tree new_decl,
>  
>    DECL_ARTIFICIAL (new_decl) = 1;
>    DECL_ABSTRACT_ORIGIN (new_decl) = DECL_ORIGIN (old_decl);
> +  copy_lto_abstract_origin_ref (new_decl, old_decl);
>    if (DECL_ORIGIN (old_decl) == old_decl)
>      old_version_node->used_as_abstract_origin = true;
>    DECL_FUNCTION_PERSONALITY (new_decl) = DECL_FUNCTION_PERSONALITY 
> (old_decl);
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri; (HRB 36809, AG Nuernberg)

Reply via email to