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.

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.

        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);
+}
-- 
2.43.7

Reply via email to