https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126355
Bug ID: 126355
Summary: IPA ICF wrapper lacks an address-bearing
DW_TAG_subprogram DIE
Product: gcc
Version: 16.1.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: debug
Assignee: unassigned at gcc dot gnu.org
Reporter: luolongjuna at gmail dot com
Target Milestone: ---
Created attachment 65111
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65111&action=edit
source file for the test case
When IPA ICF preserves an otherwise identical, address-taken function by
turning it into a wrapper, the resulting function still has a distinct
STT_FUNC symbol and emitted code, but its DW_TAG_subprogram DIE has no
DW_AT_low_pc/DW_AT_high_pc or DW_AT_ranges. There is also no separate
address-bearing concrete DIE for the wrapper.
This is a debug-info completeness issue rather than an ICF code-generation
issue: both function symbols exist at distinct addresses and execute
correctly, but the wrapper address cannot be associated with its source
function through the function DIE.
The issue reproduces without LTO.
Test case:
int
pr_icf_wrapper_a (const char *host)
{
(void) host;
return 0;
}
int
pr_icf_wrapper_b (const char *host)
{
(void) host;
return 0;
}
int (*keep_a) (const char *) = pr_icf_wrapper_a;
int (*keep_b) (const char *) = pr_icf_wrapper_b;
int
main (int argc, char **argv)
{
const char *arg = argc > 1 ? argv[1] : "x";
return keep_a (arg) + keep_b (arg);
}
Compile with:
gcc -O2 -g -gdwarf-5 -fdump-ipa-icf-details icf-wrapper.c \
-o icf-wrapper
The IPA ICF dump confirms that a wrapper was created:
Unified; Wrapper has been created.
The ELF symbol table contains both real functions at distinct addresses.
For example:
FUNC GLOBAL pr_icf_wrapper_a
FUNC GLOBAL pr_icf_wrapper_b
However, readelf --debug-dump=info shows that the DIE for
pr_icf_wrapper_b contains its name, declaration location, return type and
formal parameter, but has none of:
DW_AT_low_pc
DW_AT_high_pc
DW_AT_ranges
There is no other DW_TAG_subprogram or DW_TAG_inlined_subroutine covering
the address of pr_icf_wrapper_b.
The preliminary source analysis is:
1. ipa-icf.cc calls cgraph_node::create_wrapper() for the retained symbol.
2. create_wrapper() reuses the original source FUNCTION_DECL, which already
has an early debug DIE.
3. create_wrapper() calls expand_thunk(..., force_gimple_thunk=true).
4. expand_thunk() unconditionally sets DECL_IGNORED_P on forced GIMPLE
thunks because such thunks are normally created after early debug.
5. final.cc consequently skips debug_hooks->function_decl(), so the
existing early DIE is never updated with the wrapper address/range.
The generic expand_thunk behavior is correct for newly created late
thunks, but an IPA ICF wrapper is different: it reuses an original source
function declaration and its existing early DIE.
Saving and restoring the original DECL_IGNORED_P value around
expand_thunk() in cgraph_node::create_wrapper() makes final debug emission
attach the wrapper address to the existing source DIE while preserving the
behavior of ordinary synthetic thunks.
Reproduced with:
gcc version 16.1.1 20260515 (Red Hat 16.1.1-2)
Target: x86_64-redhat-linux
The same unconditional DECL_IGNORED_P assignment is present on GCC trunk
17.0.0 at commit:
5453f858146e60204fa3cc0fd01d9ab38ea837cc