https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126348

Longjun Luo <luolongjuna at gmail dot com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
            Summary|[LTO] Split clone and       |[LTO] Clone and inline
                   |direct children refer to    |instances refer to a
                   |abstract DIEs from          |different input's abstract
                   |different CUs               |origin than their children
             Blocks|                            |47819
           See Also|                            |https://gcc.gnu.org/bugzill
                   |                            |a/show_bug.cgi?id=128623

--- Comment #5 from Longjun Luo <luolongjuna at gmail dot com> ---
I have reduced three additional cases of the instance/child origin mismatch
reported here: nested compile-time inlining, prevailing-declaration
replacement, and mixed -g1/-g inputs.

Reproduced on x86_64 GNU/Linux with GCC 17.0.0 (g:bd921def14a4), GDB 17.2 and
binutils 2.46.1, and with Ubuntu GCC 13.3.0.

GCC 17 build configuration:
  --enable-languages=c,c++ --disable-bootstrap --disable-multilib
  --disable-nls --disable-werror --enable-checking=yes
  --without-isl --with-system-zlib

A. Nested compile-time inlining

In both inputs, leaf is inlined into middle, which is inlined into its caller
during LGEN (recorded in -fdump-ipa-clones). With link order a.o b.o, stopping
at h.h:4 in main's inline copy gives:

  With -flto:                                                 With -fno-lto:
    #0  leaf (n=<optimized out>, n=<optimized out>) at h.h:4    #0  leaf (n=1)
at h.h:4
    #1  middle (n=<optimized out>, n=<optimized out>) at h.h:9  #1  middle
(n=1) at h.h:9
    #2  main (argc=1) at b.C:4                                  #2  main
(argc=1) at b.C:4
    leaf:   n listed twice; value = 4 and <optimized out>       leaf:   n = 1,
value = 4 (once each)
    middle: n listed twice                                      middle: n = 1
(once)

In late DWARF, both inline levels in main have their DW_AT_abstract_origin
redirected to a.C while their parameters and locals still point to b.C:

  inlined middle       -> middle in a.C
    parameter n        -> middle's n in b.C
    variable result    -> middle's result in b.C
    inlined leaf       -> leaf in a.C
      parameter n      -> leaf's n in b.C
      variable value   -> leaf's value in b.C

B. Prevailing-declaration replacement

In this three-TU testcase, b.C adds -fno-strict-aliasing, so its FUNCTION_DECL
is not unified with a.C's. During prevailing-declaration fixup, lto_fixup_state
replaces the function reference in the body's decl-state with a.C's
declaration. When the body is read, BLOCK_ABSTRACT_ORIGIN resolves through that
slot to a.C's declaration, while the parameter and local-variable origins
retain b.C's early DIE references. This produces the following mismatch on the
two inline instances of Buffer::printable_length from b.C:

  inlined printable_length -> printable_length in a.C
    parameter this         -> this in b.C
    variable location      -> location in b.C

(Verified also in a local diagnostic build where unify_scc unconditionally
returns false while symbol resolution and prevailing-decl fixup remain active;
the -fno-lto build has no mismatch.)

C. Mixed -g1/-g inputs (lookup failure)

Here a.C is compiled with -g1 and b.C with -g. Stopping at h.h:6 in main's
inline copy of transform:

  Link order a.o b.o:             Link order b.o a.o:
    (gdb) print x                   (gdb) print x
    No symbol "x" in current        $1 = 8
    context.

With a.o first, the inline instance from b.C has its origin redirected to a.C's
-g1 subprogram DIE (which omits local/static variable DIEs), while its concrete
children still point to b.C:

  inlined transform in main -> transform in a.C (-g1, no x DIE)
    parameter n             -> n in b.C (-g)
    variable result         -> result in b.C (-g)

Reversing the link order keeps this instance and its children on b.C's -g
descriptions and restores 'print x'. (In the a.o-first link, n is also shown as
<optimized out>; that is PR128623, where the -g1 object's optimization node
disables variable tracking for b.o's functions.)

Scope and related PRs

Together with the original split-clone case, these examples show SCC
unification or prevailing-declaration replacement redirecting an instance's
origin to another input, while its parameters and locals retain their original
early DIE references.

Related classes filed separately:
- PR128616: merged COMDAT declaration and prevailing body come from different
TUs (affects the root function itself, DECL_ORIGIN(fn) == fn, and copies
inlined from it in LTRANS).
- PR128617: mixed -g0/-g links where a -g0 body borrows another TU's early DIE
or emits an originless DW_TAG_inlined_subroutine (unlike Case C above, where a
-g instance is redirected to a -g1 DIE).

Patch v4 and review thread (following v3 in comment 4):
https://inbox.sourceware.org/gcc-patches/[email protected]/

Reproducers

Save each case in a separate directory.

=== Case A ===

--- h.h ---
inline int leaf (int n)
{
  int value = n + 3;
  asm volatile ("nop" : "+r" (value) : : "memory");
  return value;
}
inline int middle (int n)
{
  int result = leaf (n);
  return result + 1;
}
__attribute__((noipa)) int local_value (int);

--- a.C ---
#include "h.h"
int local_value (int n) { return middle (n); }

--- b.C ---
#include "h.h"
int main (int argc, char **)
{
  int a = middle (argc);
  int b = local_value (argc + 1);
  return a + b != 11;
}

--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C
mode=${1:--flto}

"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" \
    -fdump-ipa-clones -c a.C -o a.o
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" \
    -fdump-ipa-clones -c b.C -o b.o
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" -flto-partition=one \
    -no-pie a.o b.o -o repro

"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
    -ex 'break h.h:4' -ex run -ex 'bt 3' \
    -ex 'info args' -ex 'info locals' \
    -ex up -ex 'info args' repro

Run with:
  CXX=g++ GDB=gdb sh run.sh -flto
  readelf --debug-dump=info --wide repro
  CXX=g++ GDB=gdb sh run.sh -fno-lto

=== Case B ===

--- h.h ---
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));

struct Buffer
{
  int length;

  __attribute__((used)) int printable_length () const
  {
    if (__builtin_expect (length < 1024, 1))
      return length;

    const Location location = { __FILE__, __func__, __LINE__ };
    note (location.file, location.line);
    note (location.function, length);
    note (location.file, length + 1);
    fail (3, "length < 1024", location);
  }
};

__attribute__((noinline)) int one (const Buffer &);

--- a.C ---
#include "h.h"

__attribute__((noinline)) int one (const Buffer &b)
{
  return b.printable_length ();
}

__attribute__((noinline)) int one_extra (const Buffer &b)
{
  return b.printable_length ();
}

--- b.C ---
#include "h.h"

void fail (int, const char *, const Location &) { __builtin_trap (); }
void note (const char *, int) { asm volatile ("" ::: "memory"); }

__attribute__((noinline)) int two (const Buffer &b)
{
  return b.printable_length ();
}

__attribute__((noinline)) int two_extra (const Buffer &b)
{
  return b.printable_length ();
}

--- main.C ---
#include "h.h"

__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);
}

--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C
mode=${1:--flto}

"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" \
    --param=max-inline-insns-auto=100 -c a.C -o a.o
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" \
    --param=max-inline-insns-auto=100 \
    -fno-strict-aliasing -c b.C -o b.o
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" \
    --param=max-inline-insns-auto=100 -c main.C -o main.o
"${CXX:-g++}" -O2 -g -gdwarf-5 "$mode" \
    --param=max-inline-insns-auto=100 -flto-partition=one \
    -no-pie a.o b.o main.o -o repro

readelf --debug-dump=info --wide repro > dwarf.txt

Run with:
  CXX=g++ sh run.sh -flto
  mv dwarf.txt dwarf-lto.txt
  CXX=g++ sh run.sh -fno-lto
  mv dwarf.txt dwarf-nonlto.txt

=== Case C ===

--- h.h ---
inline int transform (int n)
{
  static volatile int x = 7;
  int result = x + n;
  x = result;
  asm volatile ("nop" : "+r" (result) : : "memory");
  return result;
}
__attribute__((noipa)) int local_value (int);

--- a.C ---
#include "h.h"
int local_value (int n) { return transform (n); }

--- b.C ---
#include "h.h"
int main ()
{
  int a = transform (1);
  int b = local_value (2);
  return a + b != 18;
}

--- run.sh ---
#!/bin/sh
set -eu
export LC_ALL=C

"${CXX:-g++}" -O2 -g -gdwarf-5 -flto -g1 -c a.C -o a.o
"${CXX:-g++}" -O2 -g -gdwarf-5 -flto -c b.C -o b.o

"${CXX:-g++}" -O2 -g -gdwarf-5 -flto -flto-partition=one \
    -no-pie a.o b.o -o forward
"${CXX:-g++}" -O2 -g -gdwarf-5 -flto -flto-partition=one \
    -no-pie b.o a.o -o reverse

"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
    -ex 'break h.h:6' -ex run -ex 'print x' forward || true
"${GDB:-gdb}" -q -nx -batch -iex 'set debuginfod enabled off' \
    -ex 'break h.h:6' -ex run -ex 'print x' reverse

Run with:
  CXX=g++ GDB=gdb sh run.sh
  readelf --debug-dump=info --wide forward
  readelf --debug-dump=info --wide reverse


Referenced Bugs:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47819
[Bug 47819] [meta-bug] LTO debug information issues
  • [Bug debug/126348] [LTO] Clone a... luolongjuna at gmail dot com via Gcc-bugs

Reply via email to