]> git.ipfire.org Git - thirdparty/gcc.git/commit
gcov: Cache source files
authorJørgen Kvalsvik <j@lambda.is>
Thu, 4 Jul 2024 07:42:24 +0000 (09:42 +0200)
committerJørgen Kvalsvik <j@lambda.is>
Sun, 8 Sep 2024 20:23:49 +0000 (22:23 +0200)
commit1e17a11171dae2bc5d60380bf48c12ef49f59c7f
tree8967185e763e6f15948c7c6ce129fbf097a09919
parentb8cd236cbef60fc52524b41e2ca173eb12528792
gcov: Cache source files

Cache the source files as they are read, rather than discarding them at
the end of output_lines (), and move the reading of the source file to
the new function slurp.

This patch does not really change anything other than moving the file
reading out of output_file, but set gcov up for more interaction with
the source file. The motvating example is reporting coverage on
functions from different source files, notably C++ headers and
((always_inline)).

Here is an example of what gcov does today:

hello.h:
inline __attribute__((always_inline))
int hello (const char *s)
{
  if (s)
    printf ("hello, %s!\n", s);
  else
    printf ("hello, world!\n");
  return 0;
}

hello.c:
int notmain(const char *entity)
{
  return hello (entity);
}

int main()
{
  const char *empty = 0;
  if (!empty)
    hello (empty);
  else
    puts ("Goodbye!");
}

$ gcov -abc hello
function notmain called 0 returned 0% blocks executed 0%
    #####:    4:int notmain(const char *entity)
    %%%%%:    4-block 2
branch  0 never executed (fallthrough)
branch  1 never executed
        -:    5:{
    #####:    6:  return hello (entity);
    %%%%%:    6-block 7
        -:    7:}

Clearly there is a branch in notmain, but the branch comes from the
inlining of hello. This is not very obvious from looking at the output.
Here is hello.h.gcov:

        -:    3:inline __attribute__((always_inline))
        -:    4:int hello (const char *s)
        -:    5:{
    #####:    6:  if (s)
    %%%%%:    6-block 3
branch  0 never executed (fallthrough)
branch  1 never executed
    %%%%%:    6-block 2
branch  2 never executed (fallthrough)
branch  3 never executed
    #####:    7:    printf ("hello, %s!\n", s);
    %%%%%:    7-block 4
call    0 never executed
    %%%%%:    7-block 3
call    1 never executed
        -:    8:  else
    #####:    9:    printf ("hello, world!\n");
    %%%%%:    9-block 5
call    0 never executed
    %%%%%:    9-block 4
call    1 never executed
    #####:   10:  return 0;
    %%%%%:   10-block 6
    %%%%%:   10-block 5
        -:   11:}

The blocks from the different call sites have all been interleaved.

The reporting could tuned be to list the inlined function, too, like
this:

        1:    4:int notmain(const char *entity)
        -: == inlined from hello.h ==
        1:    6:  if (s)
branch  0 taken 0 (fallthrough)
branch  1 taken 1
    #####:    7:    printf ("hello, %s!\n", s);
    %%%%%:    7-block 3
call    0 never executed
        -:    8:  else
        1:    9:    printf ("hello, world!\n");
        1:    9-block 4
call    0 returned 1
        1:   10:  return 0;
        1:   10-block 5
        -: == inlined from hello.h (end) ==
        -:    5:{
        1:    6:  return hello (entity);
        1:    6-block 7
        -:    7:}

Implementing something to this effect relies on having the sources for
both files (hello.c, hello.h) available, which is what this patch sets
up.

Note that the previous reading code would leak the source file content,
and explicitly storing them is not a huge departure nor performance
implication. I verified this with valgrind:

With slurp:

$ valgrind gcov ./hello
== == Memcheck, a memory error detector
== == Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
== == Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
== == Command: ./gcc/gcov demo
== ==
File 'hello.c'
Lines executed:100.00% of 4
Creating 'hello.c.gcov'

File 'hello.h'
Lines executed:75.00% of 4
Creating 'hello.h.gcov'
== ==
== == HEAP SUMMARY:
== ==     in use at exit: 84,907 bytes in 54 blocks
== ==   total heap usage: 254 allocs, 200 frees, 137,156 bytes allocated
== ==
== == LEAK SUMMARY:
== ==    definitely lost: 1,237 bytes in 22 blocks
== ==    indirectly lost: 562 bytes in 18 blocks
== ==      possibly lost: 0 bytes in 0 blocks
== ==    still reachable: 83,108 bytes in 14 blocks
== ==                       of which reachable via heuristic:
== ==                         newarray           : 1,544 bytes in 1 blocks
== ==         suppressed: 0 bytes in 0 blocks
== == Rerun with --leak-check=full to see details of leaked memory
== ==
== == For lists of detected and suppressed errors, rerun with: -s
== == ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Without slurp:

$ valgrind gcov ./demo
== == Memcheck, a memory error detector
== == Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
== == Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
== == Command: ./gcc/gcov demo
== ==
File 'hello.c'
Lines executed:100.00% of 4
Creating 'hello.c.gcov'

File 'hello.h'
Lines executed:75.00% of 4
Creating 'hello.h.gcov'

Lines executed:87.50% of 8
== ==
== == HEAP SUMMARY:
== ==     in use at exit: 85,316 bytes in 82 blocks
== ==   total heap usage: 250 allocs, 168 frees, 137,084 bytes allocated
== ==
== == LEAK SUMMARY:
== ==    definitely lost: 1,646 bytes in 50 blocks
== ==    indirectly lost: 562 bytes in 18 blocks
== ==      possibly lost: 0 bytes in 0 blocks
== ==    still reachable: 83,108 bytes in 14 blocks
== ==                       of which reachable via heuristic:
== ==                         newarray           : 1,544 bytes in 1 blocks
== ==         suppressed: 0 bytes in 0 blocks
== == Rerun with --leak-check=full to see details of leaked memory
== ==
== == For lists of detected and suppressed errors, rerun with: -s
== == ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

gcc/ChangeLog:

* gcov.cc (release_structures): Release source_lines.
(slurp): New function.
(output_lines): Read sources with slurp.
gcc/gcov.cc