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:}

Reply via email to