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.