https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120482
Bug ID: 120482 Summary: Line coverage for inline functions combine its different callers in inconsistent ways Product: gcc Version: 16.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: gcov-profile Assignee: unassigned at gcc dot gnu.org Reporter: wentaoz5 at illinois dot edu Target Milestone: --- Hit the issue when measuring https://sources.debian.org/src/lz4/1.9.4-1/lib/lz4.c/#L776 etc. -: 776:LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) -: 777:{ 574*: 778: if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); 1176*: 779: return LZ4_hash4(LZ4_read32(p), tableType); -: 780:} The ground truth is "LZ4_hashPosition" has always exited via the 2nd return statement for 588 times. The presented line coverage seems some combination of its direct and indirect callers. Specifically, the call graph in lz4.c looks like: 7 7 ---> func2 ---> LZ4_putPosition / 7 / / func1 --------------> / \ 574 / -----------------> "574" seems to stem from one of three paths; "1176" seems to stem from the combination of 2 x (574 + 7 + 7). How to reproduce similar effects with a single file program: $ gcc --version gcc (GCC) 16.0.0 20250511 (experimental) Copyright (C) 2025 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ cat > test.c << 'EOF' int g; #define INLINE __inline__ __attribute__((__always_inline__)) INLINE int innermost(int x) { return 0; } INLINE int inner1(void) { g++; return 0; // -> 1 x direct } INLINE int inner2(void) { g++; return innermost(g); // -> (direct x 2) + indirect } // "const" or not also affects the behavior INLINE int inner3(int const x) { g++; // -> 1 x direct if (x != 3) // -> 1 x indirect return 0; return innermost(g); // -> (direct + indirect) x 2 } // Outer functions for indirectly calling inner functions void outer1(void) { inner1(); } void outer2(void) { inner2(); } void outer3(int x) { inner3(x); } int main(void) { for (int i = 0; i < 22; i++) { // indirect invocation outer1(); outer2(); outer3(3); } for (int i = 0; i < 4400; i++) { // direct invocation inner1(); inner2(); inner3(3); } } EOF $ gcc --coverage test.c -o test $ ./test $ gcov test $ cat test.c.gcov ... -: 7:INLINE int inner1(void) { 4422: 8: g++; 4400: 9: return 0; // -> 1 x direct -: 10:} -: 11: -: 12:INLINE int inner2(void) { 4422: 13: g++; 8822: 14: return innermost(g); // -> (direct x 2) + indirect -: 15:} -: 16: -: 17:// "const" or not also affects the behavior -: 18:INLINE int inner3(int const x) { 4400: 19: g++; // -> 1 x direct 22: 20: if (x != 3) // -> 1 x indirect #####: 21: return 0; 8844: 22: return innermost(g); // -> (direct + indirect) x 2 -: 23:} ... Commenting out the "const" keyword, we are getting a few other ways of combination. -: 17:// "const" or not also affects the behavior -: 18:INLINE int inner3(int /*const*/ x) { 4422: 19: g++; // -> (direct + indirect) 4400: 20: if (x != 3) // -> 1 x direct #####: 21: return 0; 8844: 22: return innermost(g); // -> (direct + indirect) x 2 -: 23:}