[Bug lto/126841] New: [LTO][IPA-ICF] -ffunction-sections emits distinct lto_priv functions into the same section

luolongjuna at gmail dot com via Gcc-bugs <[email protected]>
Newsgroups gmane.comp.gcc.bugs
Message-ID <[email protected]/bugzilla/>
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126841

            Bug ID: 126841
           Summary: [LTO][IPA-ICF] -ffunction-sections emits distinct
                    lto_priv functions into the same section
           Product: gcc
           Version: 16.1.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: lto
          Assignee: unassigned at gcc dot gnu.org
          Reporter: luolongjuna at gmail dot com
  Target Milestone: ---

With full LTO and -ffunction-sections, GCC can emit two distinct local
functions, with separate bodies and different section-relative addresses,
into the same native .text section.

Environment:

  gcc version 16.1.1 20260515 (Red Hat 16.1.1-2) (GCC)
  Target: x86_64-redhat-linux

Reduced reproducer
==================

The reproducer contains three separate translation units. Each translation
unit defines an identically named file-local function and stores its address
in a different global callback variable.

a.c:

  static int same_fn(int x)
  {
          return x * 33 + 7;
  }

  int (*a_callback)(int) = same_fn;

b.c:

  static int same_fn(int x)
  {
          return x * 33 + 7;
  }

  int (*b_callback)(int) = same_fn;

c.c:

  static int same_fn(int x)
  {
          return x * 33 + 7;
  }

  int (*c_callback)(int) = same_fn;

Compile and link:

  gcc -O2 -flto -ffunction-sections -fPIC -c a.c -o a.o
  gcc -O2 -flto -ffunction-sections -fPIC -c b.c -o b.o
  gcc -O2 -flto -ffunction-sections -fPIC -c c.c -o c.o

  gcc -shared -O2 -flto \
      -ffunction-sections -save-temps=obj \
      a.o b.o c.o -o repro.so

Inspect the generated LTRANS assembly:

  grep -nE '\.section.*same_fn|same_fn\.lto_priv' \
      repro.so.ltrans0.ltrans.s

Observed result
===============

The relevant LTRANS output is:

  .section .text.same_fn.lto_priv.0,"ax",@progbits
  same_fn.lto_priv.0:

  .section .text.same_fn,"ax",@progbits
  same_fn.lto_priv.1:
  ...
  same_fn.lto_priv.2:

The assembly can be converted back to an ELF relocatable object for an
unambiguous section-index check:

  gcc -c repro.so.ltrans0.ltrans.s -o ltrans-native.o
  readelf -Ws ltrans-native.o | grep same_fn
  readelf -SW ltrans-native.o | grep text.same_fn

Relevant output:

     3: 0000000000000000    10 FUNC LOCAL DEFAULT 4 same_fn.lto_priv.0
     5: 0000000000000000    10 FUNC LOCAL DEFAULT 5 same_fn.lto_priv.1
     6: 0000000000000010    10 FUNC LOCAL DEFAULT 5 same_fn.lto_priv.2

  [4] .text.same_fn.lto_priv.0 PROGBITS ... size 0x0a
  [5] .text.same_fn            PROGBITS ... size 0x1a

Thus same_fn.lto_priv.1 and same_fn.lto_priv.2 are not two aliases for one
folded body. They are separate emitted function bodies at offsets 0x0 and
0x10 in the same .text.same_fn section.

Expected result
===============

Because -ffunction-sections is enabled and the functions remain distinct
emitted bodies, each body should be placed in a distinct native section, for
example:

  .text.same_fn.lto_priv.0
  .text.same_fn.lto_priv.1
  .text.same_fn.lto_priv.2

Control result
==============

Adding -fno-ipa-icf to each of the three compile commands and to the link
command produces the expected three separate sections:

  .text.same_fn.lto_priv.0
  .text.same_fn.lto_priv.1
  .text.same_fn.lto_priv.2

The positive reproducer was rebuilt 30 times in separate clean directories
and reproduced 30/30 times. All 30 generated LTRANS assembly files were
byte-identical. The -fno-ipa-icf control was also rebuilt 30 times and
produced separate sections 30/30 times, again with byte-identical LTRANS
assembly.

Real-world instance
===================

This was reduced from a Fedora Valkey 9.0.5 full-LTO build. The real source
contains three separate files--eval.c, functions.c, and scripting_engine.c--
that each define a file-local dictStrCaseHash function and store its address
in a different global dictType object.

In the emitted native object, dictStrCaseHash.lto_priv.1 and
dictStrCaseHash.lto_priv.2 are separate 41-byte functions at offsets 0x0 and
0x30, but both are placed in .text.dictStrCaseHash.

Possible cause
==============

An implicit function-section name may be retained across LTO symbol
privatization or IPA-ICF processing and not recomputed for every final
lto_priv function. This is only a hypothesis; I have not confirmed the exact
root cause.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.