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)