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.  Set max-inline-insns-auto explicitly to leave enough margin for
target-dependent size estimates and ensure that the test exercises function
splitting across targets.

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.  The test was also cross-tested on arm-linux-gnueabihf.

        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]>
---
Changes in v3:

- Set max-inline-insns-auto explicitly in the regression test.  Linaro's
  precommit CI reported the new test failing on arm-linux-gnueabihf.  At -O2,
  x86_64 and ARM both use max-inline-insns-auto=15, giving the same COMDAT
  split-size limit of 25.  The split tail is estimated at 21 on x86_64 and 26
  on ARM, so ARM hits the size guard and does not create the second split
  part.
- No change to the compiler implementation.

The targeted x86_64 test has 6 expected passes.  I also cross-tested with
arm-linux-gnueabihf using the bot's ARM options (armv7-a, Thumb, hard float,
and cortex-a9 tuning).  With the compiler fix both scans pass; without it the
second scan fails as intended.

---
 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..27e77b8082c
--- /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 --param=max-inline-insns-auto=100 } } } */
+/* { 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.55.0

Reply via email to